Remove public API's that take Promise (#11625)

Motivation:

We did recently change the Channel / ChannelHandler API to always act on the Future only. We should do the same for our handlers.

Modifications:

- Adjust http2 API
- Adjust other handlers API

Result:

Easier to use API and more consistent
This commit is contained in:
Norman Maurer 2021-08-30 13:15:14 +02:00 committed by GitHub
parent 73377f0fd5
commit d7580b526a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 895 additions and 1069 deletions

View File

@ -236,31 +236,19 @@ public abstract class WebSocketClientHandshaker {
*/
public Future<Void> handshake(Channel channel) {
requireNonNull(channel, "channel");
return handshake(channel, channel.newPromise());
}
/**
* Begins the opening handshake
*
* @param channel
* Channel
* @param promise
* the {@link Promise} to be notified when the opening handshake is sent
*/
public final Future<Void> handshake(Channel channel, final Promise<Void> promise) {
ChannelPipeline pipeline = channel.pipeline();
HttpResponseDecoder decoder = pipeline.get(HttpResponseDecoder.class);
if (decoder == null) {
HttpClientCodec codec = pipeline.get(HttpClientCodec.class);
if (codec == null) {
promise.setFailure(new IllegalStateException("ChannelPipeline does not contain " +
return channel.newFailedFuture(new IllegalStateException("ChannelPipeline does not contain " +
"an HttpResponseDecoder or HttpClientCodec"));
return promise;
}
}
FullHttpRequest request = newHandshakeRequest();
Promise<Void> promise = channel.newPromise();
channel.writeAndFlush(request).addListener(channel, (ch, future) -> {
if (future.isSuccess()) {
ChannelPipeline p = ch.pipeline();
@ -385,29 +373,12 @@ public abstract class WebSocketClientHandshaker {
* the {@link Future} which is notified once the handshake completes.
*/
public final Future<Void> processHandshake(final Channel channel, HttpResponse response) {
return processHandshake(channel, response, channel.newPromise());
}
/**
* Process the opening handshake initiated by {@link #handshake}}.
*
* @param channel
* Channel
* @param response
* HTTP response containing the closing handshake details
* @param promise
* the {@link Promise} to notify once the handshake completes.
* @return future
* the {@link Future} which is notified once the handshake completes.
*/
public final Future<Void> processHandshake(final Channel channel, HttpResponse response,
final Promise<Void> promise) {
if (response instanceof FullHttpResponse) {
try {
finishHandshake(channel, (FullHttpResponse) response);
promise.setSuccess(null);
return channel.newSucceededFuture();
} catch (Throwable cause) {
promise.setFailure(cause);
return channel.newFailedFuture(cause);
}
} else {
ChannelPipeline p = channel.pipeline();
@ -415,10 +386,12 @@ public abstract class WebSocketClientHandshaker {
if (ctx == null) {
ctx = p.context(HttpClientCodec.class);
if (ctx == null) {
return promise.setFailure(new IllegalStateException("ChannelPipeline does not contain " +
return channel.newFailedFuture(new IllegalStateException("ChannelPipeline does not contain " +
"an HttpResponseDecoder or HttpClientCodec"));
}
}
Promise<Void> promise = channel.newPromise();
// Add aggregator and ensure we feed the HttpResponse so it is aggregated. A limit of 8192 should be more
// then enough for the websockets handshake payload.
//
@ -459,8 +432,8 @@ public abstract class WebSocketClientHandshaker {
} catch (Throwable cause) {
promise.setFailure(cause);
}
return promise;
}
return promise;
}
/**

View File

@ -169,7 +169,7 @@ public abstract class WebSocketServerHandshaker {
* The {@link Future} which is notified once the opening handshake completes
*/
public Future<Void> handshake(Channel channel, FullHttpRequest req) {
return handshake(channel, req, null, channel.newPromise());
return handshake(channel, req, null);
}
/**
@ -183,13 +183,10 @@ public abstract class WebSocketServerHandshaker {
* HTTP Request
* @param responseHeaders
* Extra headers to add to the handshake response or {@code null} if no extra headers should be added
* @param promise
* the {@link Promise} to be notified when the opening handshake is done
* @return future
* the {@link Future} which is notified when the opening handshake is done
*/
public final Future<Void> handshake(Channel channel, FullHttpRequest req,
HttpHeaders responseHeaders, final Promise<Void> promise) {
public final Future<Void> handshake(Channel channel, FullHttpRequest req, HttpHeaders responseHeaders) {
if (logger.isDebugEnabled()) {
logger.debug("{} WebSocket version {} server handshake", channel, version());
@ -208,9 +205,8 @@ public abstract class WebSocketServerHandshaker {
// this means the user use an HttpServerCodec
ctx = p.context(HttpServerCodec.class);
if (ctx == null) {
promise.setFailure(
return channel.newFailedFuture(
new IllegalStateException("No HttpDecoder and no HttpServerCodec in the pipeline"));
return promise;
}
p.addBefore(ctx.name(), "wsencoder", newWebSocketEncoder());
p.addBefore(ctx.name(), "wsdecoder", newWebsocketDecoder());
@ -221,16 +217,12 @@ public abstract class WebSocketServerHandshaker {
encoderName = p.context(HttpResponseEncoder.class).name();
p.addBefore(encoderName, "wsencoder", newWebSocketEncoder());
}
channel.writeAndFlush(response).addListener(channel, (ch, future) -> {
return channel.writeAndFlush(response).addListener(channel, (ch, future) -> {
if (future.isSuccess()) {
ChannelPipeline p1 = ch.pipeline();
p1.remove(encoderName);
promise.setSuccess(null);
} else {
promise.setFailure(future.cause());
}
});
return promise;
}
/**
@ -245,7 +237,7 @@ public abstract class WebSocketServerHandshaker {
* The {@link Future} which is notified once the opening handshake completes
*/
public Future<Void> handshake(Channel channel, HttpRequest req) {
return handshake(channel, req, null, channel.newPromise());
return handshake(channel, req, null);
}
/**
@ -259,16 +251,14 @@ public abstract class WebSocketServerHandshaker {
* HTTP Request
* @param responseHeaders
* Extra headers to add to the handshake response or {@code null} if no extra headers should be added
* @param promise
* the {@link Promise} to be notified when the opening handshake is done
* @return future
* the {@link Future} which is notified when the opening handshake is done
*/
public final Future<Void> handshake(final Channel channel, HttpRequest req,
final HttpHeaders responseHeaders, final Promise<Void> promise) {
final HttpHeaders responseHeaders) {
if (req instanceof FullHttpRequest) {
return handshake(channel, (FullHttpRequest) req, responseHeaders, promise);
return handshake(channel, (FullHttpRequest) req, responseHeaders);
}
if (logger.isDebugEnabled()) {
logger.debug("{} WebSocket version {} server handshake", channel, version());
@ -279,11 +269,12 @@ public abstract class WebSocketServerHandshaker {
// this means the user use an HttpServerCodec
ctx = p.context(HttpServerCodec.class);
if (ctx == null) {
promise.setFailure(
return channel.newFailedFuture(
new IllegalStateException("No HttpDecoder and no HttpServerCodec in the pipeline"));
return promise;
}
}
Promise<Void> promise = channel.newPromise();
// Add aggregator and ensure we feed the HttpRequest so it is aggregated. A limit o 8192 should be more then
// enough for the websockets handshake payload.
//
@ -295,7 +286,7 @@ public abstract class WebSocketServerHandshaker {
protected void messageReceived(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
// Remove ourself and do the actual handshake
ctx.pipeline().remove(this);
handshake(channel, msg, responseHeaders, promise);
handshake(channel, msg, responseHeaders);
}
@Override

View File

@ -100,7 +100,7 @@ public abstract class WebSocketServerHandshakerTest {
"ws://example.com/chat");
request.headers().set("x-client-header", "value");
try {
serverHandshaker.handshake(null, request, null, null);
serverHandshaker.handshake(null, request, null);
} catch (WebSocketServerHandshakeException exception) {
assertNotNull(exception.getMessage());
assertEquals(request.headers(), exception.request().headers());

View File

@ -143,12 +143,12 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
@Override
public Future<Void> writeData(final ChannelHandlerContext ctx, final int streamId, ByteBuf data, int padding,
final boolean endOfStream, Promise<Void> promise) {
final boolean endOfStream) {
final Http2Stream stream = connection().stream(streamId);
final EmbeddedChannel channel = stream == null ? null : (EmbeddedChannel) stream.getProperty(propertyKey);
if (channel == null) {
// The compressor may be null if no compatible encoding type was found in this stream's headers
return super.writeData(ctx, streamId, data, padding, endOfStream, promise);
return super.writeData(ctx, streamId, data, padding, endOfStream);
}
try {
@ -161,13 +161,13 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
buf = nextReadableBuf(channel);
}
return super.writeData(ctx, streamId, buf == null ? Unpooled.EMPTY_BUFFER : buf, padding,
true, promise);
true);
}
// END_STREAM is not set and the assumption is data is still forthcoming.
promise.setSuccess(null);
return promise;
return ctx.newSucceededFuture();
}
Promise<Void> promise = ctx.newPromise();
PromiseCombiner combiner = new PromiseCombiner(ctx.executor());
for (;;) {
ByteBuf nextBuf = nextReadableBuf(channel);
@ -177,9 +177,8 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
compressedEndOfStream = nextBuf == null;
}
Promise<Void> bufPromise = ctx.newPromise();
combiner.add(bufPromise);
super.writeData(ctx, streamId, buf, padding, compressedEndOfStream, bufPromise);
Future<Void> future = super.writeData(ctx, streamId, buf, padding, compressedEndOfStream);
combiner.add(future);
if (nextBuf == null) {
break;
}
@ -188,56 +187,54 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
buf = nextBuf;
}
combiner.finish(promise);
return promise;
} catch (Throwable cause) {
promise.tryFailure(cause);
return ctx.newFailedFuture(cause);
} finally {
if (endOfStream) {
cleanup(stream, channel);
}
}
return promise;
}
@Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
boolean endStream, Promise<Void> promise) {
boolean endStream) {
try {
// Determine if compression is required and sanitize the headers.
EmbeddedChannel compressor = newCompressor(ctx, headers, endStream);
// Write the headers and create the stream object.
Future<Void> future = super.writeHeaders(ctx, streamId, headers, padding, endStream, promise);
Future<Void> future = super.writeHeaders(ctx, streamId, headers, padding, endStream);
// After the stream object has been created, then attach the compressor as a property for data compression.
bindCompressorToStream(compressor, streamId);
return future;
} catch (Throwable e) {
promise.tryFailure(e);
return ctx.newFailedFuture(e);
}
return promise;
}
@Override
public Future<Void> writeHeaders(final ChannelHandlerContext ctx, final int streamId, final Http2Headers headers,
final int streamDependency, final short weight, final boolean exclusive, final int padding,
final boolean endOfStream, final Promise<Void> promise) {
final boolean endOfStream) {
try {
// Determine if compression is required and sanitize the headers.
EmbeddedChannel compressor = newCompressor(ctx, headers, endOfStream);
// Write the headers and create the stream object.
Future<Void> future = super.writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive,
padding, endOfStream, promise);
padding, endOfStream);
// After the stream object has been created, then attach the compressor as a property for data compression.
bindCompressorToStream(compressor, streamId);
return future;
} catch (Throwable e) {
promise.tryFailure(e);
return ctx.newFailedFuture(e);
}
return promise;
}
/**

View File

@ -17,7 +17,6 @@ package io.netty.handler.codec.http2;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.UnstableApi;
import static java.util.Objects.requireNonNull;
@ -35,73 +34,70 @@ public class DecoratingHttp2FrameWriter implements Http2FrameWriter {
@Override
public Future<Void> writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endStream, Promise<Void> promise) {
return delegate.writeData(ctx, streamId, data, padding, endStream, promise);
boolean endStream) {
return delegate.writeData(ctx, streamId, data, padding, endStream);
}
@Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
boolean endStream, Promise<Void> promise) {
return delegate.writeHeaders(ctx, streamId, headers, padding, endStream, promise);
boolean endStream) {
return delegate.writeHeaders(ctx, streamId, headers, padding, endStream);
}
@Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding,
boolean endStream, Promise<Void> promise) {
boolean endStream) {
return delegate
.writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream, promise);
.writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream);
}
@Override
public Future<Void> writePriority(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
boolean exclusive, Promise<Void> promise) {
return delegate.writePriority(ctx, streamId, streamDependency, weight, exclusive, promise);
boolean exclusive) {
return delegate.writePriority(ctx, streamId, streamDependency, weight, exclusive);
}
@Override
public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode,
Promise<Void> promise) {
return delegate.writeRstStream(ctx, streamId, errorCode, promise);
public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode) {
return delegate.writeRstStream(ctx, streamId, errorCode);
}
@Override
public Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings, Promise<Void> promise) {
return delegate.writeSettings(ctx, settings, promise);
public Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings) {
return delegate.writeSettings(ctx, settings);
}
@Override
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx, Promise<Void> promise) {
return delegate.writeSettingsAck(ctx, promise);
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx) {
return delegate.writeSettingsAck(ctx);
}
@Override
public Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data, Promise<Void> promise) {
return delegate.writePing(ctx, ack, data, promise);
public Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data) {
return delegate.writePing(ctx, ack, data);
}
@Override
public Future<Void> writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
Http2Headers headers, int padding, Promise<Void> promise) {
return delegate.writePushPromise(ctx, streamId, promisedStreamId, headers, padding, promise);
Http2Headers headers, int padding) {
return delegate.writePushPromise(ctx, streamId, promisedStreamId, headers, padding);
}
@Override
public Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData,
Promise<Void> promise) {
return delegate.writeGoAway(ctx, lastStreamId, errorCode, debugData, promise);
public Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData) {
return delegate.writeGoAway(ctx, lastStreamId, errorCode, debugData);
}
@Override
public Future<Void> writeWindowUpdate(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement,
Promise<Void> promise) {
return delegate.writeWindowUpdate(ctx, streamId, windowSizeIncrement, promise);
public Future<Void> writeWindowUpdate(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) {
return delegate.writeWindowUpdate(ctx, streamId, windowSizeIncrement);
}
@Override
public Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
ByteBuf payload, Promise<Void> promise) {
return delegate.writeFrame(ctx, frameType, streamId, flags, payload, promise);
ByteBuf payload) {
return delegate.writeFrame(ctx, frameType, streamId, flags, payload);
}
@Override

View File

@ -20,7 +20,6 @@ import io.netty.handler.codec.http2.Http2Stream.State;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.collection.IntObjectMap;
import io.netty.util.collection.IntObjectMap.PrimitiveEntry;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.UnaryPromiseNotifier;
import io.netty.util.internal.EmptyArrays;
@ -116,7 +115,7 @@ public class DefaultHttp2Connection implements Http2Connection {
}
@Override
public Future<Void> close(final Promise<Void> promise) {
public void close(final Promise<Void> promise) {
requireNonNull(promise, "promise");
// Since we allow this method to be called multiple times, we must make sure that all the promises are notified
// when all streams are removed and the close operation completes.
@ -131,7 +130,7 @@ public class DefaultHttp2Connection implements Http2Connection {
}
if (isStreamMapEmpty()) {
promise.trySuccess(null);
return promise;
return;
}
Iterator<PrimitiveEntry<Http2Stream>> itr = streamMap.entries().iterator();
@ -162,7 +161,6 @@ public class DefaultHttp2Connection implements Http2Connection {
}
}
}
return closePromise;
}
@Override

View File

@ -498,7 +498,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
// Acknowledge receipt of the settings. We should do this before we process the settings to ensure our
// remote peer applies these settings before any subsequent frames that we may send which depend upon
// these new settings. See https://github.com/netty/netty/issues/6520.
encoder.writeSettingsAck(ctx, ctx.newPromise());
encoder.writeSettingsAck(ctx);
encoder.remoteSettings(settings);
} else {
@ -512,7 +512,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
if (autoAckPing) {
// Send an ack back to the remote client.
encoder.writePing(ctx, true, data, ctx.newPromise());
encoder.writePing(ctx, true, data);
}
listener.onPingRead(ctx, data);
}

View File

@ -120,7 +120,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
@Override
public Future<Void> writeData(final ChannelHandlerContext ctx, final int streamId, ByteBuf data, int padding,
final boolean endOfStream, Promise<Void> promise) {
final boolean endOfStream) {
final Http2Stream stream;
try {
stream = requireStream(streamId);
@ -136,9 +136,10 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
}
} catch (Throwable e) {
data.release();
return promise.setFailure(e);
return ctx.newFailedFuture(e);
}
Promise<Void> promise = ctx.newPromise();
// Hand control of the frame to the flow controller.
flowController().addFlowControlled(stream,
new FlowControlledData(stream, data, padding, endOfStream, promise, ctx.channel()));
@ -147,8 +148,8 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
@Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
boolean endStream, Promise<Void> promise) {
return writeHeaders0(ctx, streamId, headers, false, 0, (short) 0, false, padding, endStream, promise);
boolean endStream) {
return writeHeaders0(ctx, streamId, headers, false, 0, (short) 0, false, padding, endStream);
}
private static boolean validateHeadersSentState(Http2Stream stream, Http2Headers headers, boolean isServer,
@ -163,10 +164,9 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
@Override
public Future<Void> writeHeaders(final ChannelHandlerContext ctx, final int streamId,
final Http2Headers headers, final int streamDependency, final short weight,
final boolean exclusive, final int padding, final boolean endOfStream,
Promise<Void> promise) {
final boolean exclusive, final int padding, final boolean endOfStream) {
return writeHeaders0(ctx, streamId, headers, true, streamDependency,
weight, exclusive, padding, endOfStream, promise);
weight, exclusive, padding, endOfStream);
}
/**
@ -177,19 +177,19 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
Http2Headers headers, final boolean hasPriority,
int streamDependency, final short weight,
boolean exclusive, final int padding,
boolean endOfStream, Promise<Void> promise) {
boolean endOfStream) {
if (hasPriority) {
return frameWriter.writeHeaders(ctx, streamId, headers, streamDependency,
weight, exclusive, padding, endOfStream, promise);
weight, exclusive, padding, endOfStream);
}
return frameWriter.writeHeaders(ctx, streamId, headers, padding, endOfStream, promise);
return frameWriter.writeHeaders(ctx, streamId, headers, padding, endOfStream);
}
private Future<Void> writeHeaders0(final ChannelHandlerContext ctx, final int streamId,
final Http2Headers headers, final boolean hasPriority,
final int streamDependency, final short weight,
final boolean exclusive, final int padding,
final boolean endOfStream, Promise<Void> promise) {
final boolean endOfStream) {
try {
Http2Stream stream = connection.stream(streamId);
if (stream == null) {
@ -202,8 +202,8 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
stream = connection.local().createStream(streamId, /*endOfStream*/ false);
} catch (Http2Exception cause) {
if (connection.remote().mayHaveCreatedStream(streamId)) {
promise.tryFailure(new IllegalStateException("Stream no longer exists: " + streamId, cause));
return promise;
return ctx.newFailedFuture(
new IllegalStateException("Stream no longer exists: " + streamId, cause));
}
throw cause;
}
@ -231,7 +231,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream);
Future<Void> future = sendHeaders(frameWriter, ctx, streamId, headers, hasPriority, streamDependency,
weight, exclusive, padding, endOfStream, promise);
weight, exclusive, padding, endOfStream);
// Writing headers may fail during the encode state if they violate HPACK limits.
@ -261,6 +261,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
return future;
} else {
Promise<Void> promise = ctx.newPromise();
// Pass headers to the flow-controller so it can maintain their sequence relative to DATA frames.
flowController.addFlowControlled(stream,
new FlowControlledHeaders(stream, headers, hasPriority, streamDependency,
@ -269,27 +270,24 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
}
} catch (Throwable t) {
lifecycleManager.onError(ctx, true, t);
promise.tryFailure(t);
return promise;
return ctx.newFailedFuture(t);
}
}
@Override
public Future<Void> writePriority(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
boolean exclusive, Promise<Void> promise) {
return frameWriter.writePriority(ctx, streamId, streamDependency, weight, exclusive, promise);
boolean exclusive) {
return frameWriter.writePriority(ctx, streamId, streamDependency, weight, exclusive);
}
@Override
public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode,
Promise<Void> promise) {
public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode) {
// Delegate to the lifecycle manager for proper updating of connection state.
return lifecycleManager.resetStream(ctx, streamId, errorCode, promise);
return lifecycleManager.resetStream(ctx, streamId, errorCode);
}
@Override
public Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings,
Promise<Void> promise) {
public Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings) {
outstandingLocalSettingsQueue.add(settings);
try {
Boolean pushEnabled = settings.pushEnabled();
@ -297,27 +295,28 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
throw connectionError(PROTOCOL_ERROR, "Server sending SETTINGS frame with ENABLE_PUSH specified");
}
} catch (Throwable e) {
return promise.setFailure(e);
return ctx.newFailedFuture(e);
}
return frameWriter.writeSettings(ctx, settings, promise);
return frameWriter.writeSettings(ctx, settings);
}
@Override
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx, Promise<Void> promise) {
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx) {
if (outstandingRemoteSettingsQueue == null) {
return frameWriter.writeSettingsAck(ctx, promise);
return frameWriter.writeSettingsAck(ctx);
}
Http2Settings settings = outstandingRemoteSettingsQueue.poll();
if (settings == null) {
return promise.setFailure(new Http2Exception(INTERNAL_ERROR, "attempted to write a SETTINGS ACK with no " +
return ctx.newFailedFuture(new Http2Exception(INTERNAL_ERROR, "attempted to write a SETTINGS ACK with no " +
" pending SETTINGS"));
}
SimpleChannelPromiseAggregator aggregator = new SimpleChannelPromiseAggregator(promise, ctx.executor());
SimpleChannelPromiseAggregator aggregator =
new SimpleChannelPromiseAggregator(ctx.newPromise(), ctx.executor());
// Acknowledge receipt of the settings. We should do this before we process the settings to ensure our
// remote peer applies these settings before any subsequent frames that we may send which depend upon
// these new settings. See https://github.com/netty/netty/issues/6520.
frameWriter.writeSettingsAck(ctx, aggregator.newPromise());
frameWriter.writeSettingsAck(ctx).cascadeTo(aggregator.newPromise());
// We create a "new promise" to make sure that status from both the write and the application are taken into
// account independently.
@ -333,13 +332,13 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
}
@Override
public Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data, Promise<Void> promise) {
return frameWriter.writePing(ctx, ack, data, promise);
public Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data) {
return frameWriter.writePing(ctx, ack, data);
}
@Override
public Future<Void> writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
Http2Headers headers, int padding, Promise<Void> promise) {
Http2Headers headers, int padding) {
try {
if (connection.goAwayReceived()) {
throw connectionError(PROTOCOL_ERROR, "Sending PUSH_PROMISE after GO_AWAY received.");
@ -349,8 +348,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
// Reserve the promised stream.
connection.local().reservePushStream(promisedStreamId, stream);
Future<Void> future = frameWriter.writePushPromise(ctx, streamId, promisedStreamId, headers, padding,
promise);
Future<Void> future = frameWriter.writePushPromise(ctx, streamId, promisedStreamId, headers, padding);
// Writing headers may fail during the encode state if they violate HPACK limits.
if (future.isSuccess() || !future.isDone()) {
// This just sets internal stream state which is used elsewhere in the codec and doesn't
@ -368,28 +366,25 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
return future;
} catch (Throwable t) {
lifecycleManager.onError(ctx, true, t);
promise.tryFailure(t);
return promise;
return ctx.newFailedFuture(t);
}
}
@Override
public Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData,
Promise<Void> promise) {
return lifecycleManager.goAway(ctx, lastStreamId, errorCode, debugData, promise);
public Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData) {
return lifecycleManager.goAway(ctx, lastStreamId, errorCode, debugData);
}
@Override
public Future<Void> writeWindowUpdate(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement,
Promise<Void> promise) {
return promise.setFailure(new UnsupportedOperationException("Use the Http2[Inbound|Outbound]FlowController" +
public Future<Void> writeWindowUpdate(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) {
return ctx.newFailedFuture(new UnsupportedOperationException("Use the Http2[Inbound|Outbound]FlowController" +
" objects to control window sizes"));
}
@Override
public Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
ByteBuf payload, Promise<Void> promise) {
return frameWriter.writeFrame(ctx, frameType, streamId, flags, payload, promise);
ByteBuf payload) {
return frameWriter.writeFrame(ctx, frameType, streamId, flags, payload);
}
@Override
@ -424,7 +419,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
@Override
public void consumeReceivedSettings(Http2Settings settings) {
if (outstandingRemoteSettingsQueue == null) {
outstandingRemoteSettingsQueue = new ArrayDeque<Http2Settings>(2);
outstandingRemoteSettingsQueue = new ArrayDeque<>(2);
}
outstandingRemoteSettingsQueue.add(settings);
}
@ -508,7 +503,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
// Write the frame(s).
frameWriter().writeData(ctx, stream.id(), toWrite, writablePadding,
endOfStream && size() == 0, writePromise);
endOfStream && size() == 0).cascadeTo(writePromise);
}
@Override
@ -580,7 +575,8 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
promise.addListener(this);
Future<Void> f = sendHeaders(frameWriter, ctx, stream.id(), headers, hasPriority, streamDependency,
weight, exclusive, padding, endOfStream, promise);
weight, exclusive, padding, endOfStream);
f.cascadeTo(promise);
// Writing headers may fail during the encode state if they violate HPACK limits.
if (!f.isFailed()) { // "not failed" means either not done, or completed successfully.
// This just sets internal stream state which is used elsewhere in the codec and doesn't
@ -621,7 +617,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
}
@Override
public void operationComplete(Future<? extends Void> future) throws Exception {
public void operationComplete(Future<? extends Void> future) {
if (future.isFailed()) {
error(flowController().channelHandlerContext(), future.cause());
}

View File

@ -21,7 +21,6 @@ import io.netty.handler.codec.http2.Http2CodecUtil.SimpleChannelPromiseAggregato
import io.netty.handler.codec.http2.Http2FrameWriter.Configuration;
import io.netty.handler.codec.http2.Http2HeadersEncoder.SensitivityDetector;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.UnstableApi;
import static io.netty.buffer.Unpooled.directBuffer;
@ -133,9 +132,9 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
@Override
public Future<Void> writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
int padding, boolean endStream, Promise<Void> promise) {
int padding, boolean endStream) {
final SimpleChannelPromiseAggregator promiseAggregator =
new SimpleChannelPromiseAggregator(promise, ctx.executor());
new SimpleChannelPromiseAggregator(ctx.newPromise(), ctx.executor());
ByteBuf frameHeader = null;
try {
verifyStreamId(streamId, STREAM_ID);
@ -242,7 +241,9 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
// Use a try/finally here in case the data has been released before calling this method. This is not
// necessary above because we internally allocate frameHeader.
try {
if (data != null) {
if (data != null &&
// Check if the data was released already.
data.refCnt() > 0) {
data.release();
}
} finally {
@ -256,22 +257,22 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
@Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int padding, boolean endStream, Promise<Void> promise) {
Http2Headers headers, int padding, boolean endStream) {
return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
false, 0, (short) 0, false, promise);
false, 0, (short) 0, false);
}
@Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int streamDependency, short weight, boolean exclusive,
int padding, boolean endStream, Promise<Void> promise) {
int padding, boolean endStream) {
return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
true, streamDependency, weight, exclusive, promise);
true, streamDependency, weight, exclusive);
}
@Override
public Future<Void> writePriority(ChannelHandlerContext ctx, int streamId,
int streamDependency, short weight, boolean exclusive, Promise<Void> promise) {
int streamDependency, short weight, boolean exclusive) {
try {
verifyStreamId(streamId, STREAM_ID);
verifyStreamOrConnectionId(streamDependency, STREAM_DEPENDENCY);
@ -282,15 +283,14 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
buf.writeInt(exclusive ? (int) (0x80000000L | streamDependency) : streamDependency);
// Adjust the weight so that it fits into a single byte on the wire.
buf.writeByte(weight - 1);
return ctx.write(buf).cascadeTo(promise);
return ctx.write(buf);
} catch (Throwable t) {
return promise.setFailure(t);
return ctx.newFailedFuture(t);
}
}
@Override
public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode,
Promise<Void> promise) {
public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode) {
try {
verifyStreamId(streamId, STREAM_ID);
verifyErrorCode(errorCode);
@ -298,15 +298,14 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
ByteBuf buf = ctx.alloc().buffer(RST_STREAM_FRAME_LENGTH);
writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, RST_STREAM, new Http2Flags(), streamId);
buf.writeInt((int) errorCode);
return ctx.write(buf).cascadeTo(promise);
return ctx.write(buf);
} catch (Throwable t) {
return promise.setFailure(t);
return ctx.newFailedFuture(t);
}
}
@Override
public Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings,
Promise<Void> promise) {
public Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings) {
try {
requireNonNull(settings, "settings");
int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
@ -316,39 +315,40 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
buf.writeChar(entry.key());
buf.writeInt(entry.value().intValue());
}
return ctx.write(buf).cascadeTo(promise);
return ctx.write(buf);
} catch (Throwable t) {
return promise.setFailure(t);
return ctx.newFailedFuture(t);
}
}
@Override
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx, Promise<Void> promise) {
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx) {
try {
ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
writeFrameHeaderInternal(buf, 0, SETTINGS, new Http2Flags().ack(true), 0);
return ctx.write(buf).cascadeTo(promise);
return ctx.write(buf);
} catch (Throwable t) {
return promise.setFailure(t);
return ctx.newFailedFuture(t);
}
}
@Override
public Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data, Promise<Void> promise) {
public Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data) {
Http2Flags flags = ack ? new Http2Flags().ack(true) : new Http2Flags();
ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH + PING_FRAME_PAYLOAD_LENGTH);
// Assume nothing below will throw until buf is written. That way we don't have to take care of ownership
// in the catch block.
writeFrameHeaderInternal(buf, PING_FRAME_PAYLOAD_LENGTH, PING, flags, 0);
buf.writeLong(data);
return ctx.write(buf).cascadeTo(promise);
return ctx.write(buf);
}
@Override
public Future<Void> writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
Http2Headers headers, int padding, Promise<Void> promise) {
Http2Headers headers, int padding) {
ByteBuf headerBlock = null;
SimpleChannelPromiseAggregator promiseAggregator = new SimpleChannelPromiseAggregator(promise, ctx.executor());
SimpleChannelPromiseAggregator promiseAggregator =
new SimpleChannelPromiseAggregator(ctx.newPromise(), ctx.executor());
try {
verifyStreamId(streamId, STREAM_ID);
verifyStreamId(promisedStreamId, "Promised Stream ID");
@ -403,8 +403,9 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
@Override
public Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
ByteBuf debugData, Promise<Void> promise) {
SimpleChannelPromiseAggregator promiseAggregator = new SimpleChannelPromiseAggregator(promise, ctx.executor());
ByteBuf debugData) {
SimpleChannelPromiseAggregator promiseAggregator =
new SimpleChannelPromiseAggregator(ctx.newPromise(), ctx.executor());
try {
verifyStreamOrConnectionId(lastStreamId, "Last Stream ID");
verifyErrorCode(errorCode);
@ -437,7 +438,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
@Override
public Future<Void> writeWindowUpdate(ChannelHandlerContext ctx, int streamId,
int windowSizeIncrement, Promise<Void> promise) {
int windowSizeIncrement) {
try {
verifyStreamOrConnectionId(streamId, STREAM_ID);
verifyWindowSizeIncrement(windowSizeIncrement);
@ -445,16 +446,17 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
ByteBuf buf = ctx.alloc().buffer(WINDOW_UPDATE_FRAME_LENGTH);
writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, WINDOW_UPDATE, new Http2Flags(), streamId);
buf.writeInt(windowSizeIncrement);
return ctx.write(buf).cascadeTo(promise);
return ctx.write(buf);
} catch (Throwable t) {
return promise.setFailure(t);
return ctx.newFailedFuture(t);
}
}
@Override
public Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
Http2Flags flags, ByteBuf payload, Promise<Void> promise) {
SimpleChannelPromiseAggregator promiseAggregator = new SimpleChannelPromiseAggregator(promise, ctx.executor());
Http2Flags flags, ByteBuf payload) {
SimpleChannelPromiseAggregator promiseAggregator =
new SimpleChannelPromiseAggregator(ctx.newPromise(), ctx.executor());
try {
verifyStreamOrConnectionId(streamId, STREAM_ID);
ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
@ -481,9 +483,10 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
private Future<Void> writeHeadersInternal(ChannelHandlerContext ctx,
int streamId, Http2Headers headers, int padding, boolean endStream,
boolean hasPriority, int streamDependency, short weight, boolean exclusive, Promise<Void> promise) {
boolean hasPriority, int streamDependency, short weight, boolean exclusive) {
ByteBuf headerBlock = null;
SimpleChannelPromiseAggregator promiseAggregator = new SimpleChannelPromiseAggregator(promise, ctx.executor());
SimpleChannelPromiseAggregator promiseAggregator =
new SimpleChannelPromiseAggregator(ctx.newPromise(), ctx.executor());
try {
verifyStreamId(streamId, STREAM_ID);
if (hasPriority) {

View File

@ -483,7 +483,7 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
}
// Send a window update for the stream/connection.
frameWriter.writeWindowUpdate(ctx, stream.id(), deltaWindowSize, ctx.newPromise());
frameWriter.writeWindowUpdate(ctx, stream.id(), deltaWindowSize);
}
}

View File

@ -253,10 +253,10 @@ public interface Http2Connection {
* all streams that exists (active or otherwise) will be closed and removed.
* <p>Note if iterating active streams via {@link #forEachActiveStream(Http2StreamVisitor)} and an exception is
* thrown it is necessary to call this method again to ensure the close completes.
*
* @param promise Will be completed when all streams have been removed, and listeners have been notified.
* @return A future that will be completed when all streams have been removed, and listeners have been notified.
*/
Future<Void> close(Promise<Void> promise);
void close(Promise<Void> promise);
/**
* Creates a new key that is unique within this {@link Http2Connection}.

View File

@ -61,9 +61,8 @@ public interface Http2ConnectionEncoder extends Http2FrameWriter {
/**
* Writes the given data to the internal {@link Http2FrameWriter} without performing any
* state checks on the connection/stream.
* @return
*/
@Override
Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
Http2Flags flags, ByteBuf payload, Promise<Void> promise);
Http2Flags flags, ByteBuf payload);
}

View File

@ -356,7 +356,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
}
// Both client and server must send their initial settings.
encoder.writeSettings(ctx, initialSettings, ctx.newPromise())
encoder.writeSettings(ctx, initialSettings)
.addListener(ctx.channel(), ChannelFutureListeners.CLOSE_ON_FAILURE);
if (isClient) {
@ -450,7 +450,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
// a GO_AWAY has been sent we send a empty buffer just so we can wait to close until all other data has been
// flushed to the OS.
// https://github.com/netty/netty/issues/5307
Future<Void> f = connection().goAwaySent() ? ctx.write(EMPTY_BUFFER) : goAway(ctx, null, ctx.newPromise());
Future<Void> f = connection().goAwaySent() ? ctx.write(EMPTY_BUFFER) : goAway(ctx, null);
ctx.flush();
Promise<Void> promise = ctx.newPromise();
doGracefulShutdown(ctx, f, promise);
@ -626,7 +626,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
}
Promise<Void> promise = ctx.newPromise();
Future<Void> future = goAway(ctx, http2Ex, ctx.newPromise());
Future<Void> future = goAway(ctx, http2Ex);
if (http2Ex.shutdownHint() == Http2Exception.ShutdownHint.GRACEFUL_SHUTDOWN) {
doGracefulShutdown(ctx, future, promise);
} else {
@ -663,7 +663,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
try {
stream = encoder.connection().remote().createStream(streamId, true);
} catch (Http2Exception e) {
resetUnknownStream(ctx, streamId, http2Ex.error().code(), ctx.newPromise());
resetUnknownStream(ctx, streamId, http2Ex.error().code());
return;
}
}
@ -680,10 +680,10 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
if (stream == null) {
if (!outbound || connection().local().mayHaveCreatedStream(streamId)) {
resetUnknownStream(ctx, streamId, http2Ex.error().code(), ctx.newPromise());
resetUnknownStream(ctx, streamId, http2Ex.error().code());
}
} else {
resetStream(ctx, stream, http2Ex.error().code(), ctx.newPromise());
resetStream(ctx, stream, http2Ex.error().code());
}
}
@ -695,7 +695,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
* @param stream the Http2Stream on which the header was received
*/
protected void handleServerHeaderDecodeSizeError(ChannelHandlerContext ctx, Http2Stream stream) {
encoder().writeHeaders(ctx, stream.id(), HEADERS_TOO_LARGE_HEADERS, 0, true, ctx.newPromise());
encoder().writeHeaders(ctx, stream.id(), HEADERS_TOO_LARGE_HEADERS, 0, true);
}
protected Http2FrameWriter frameWriter() {
@ -707,9 +707,8 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
* triggered by the first frame of a stream being invalid. That is, there was an error reading the frame before
* we could create a new stream.
*/
private Future<Void> resetUnknownStream(final ChannelHandlerContext ctx, int streamId, long errorCode,
Promise<Void> promise) {
Future<Void> future = frameWriter().writeRstStream(ctx, streamId, errorCode, promise);
private Future<Void> resetUnknownStream(final ChannelHandlerContext ctx, int streamId, long errorCode) {
Future<Void> future = frameWriter().writeRstStream(ctx, streamId, errorCode);
if (future.isDone()) {
closeConnectionOnError(ctx, future);
} else {
@ -719,21 +718,20 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
}
@Override
public Future<Void> resetStream(final ChannelHandlerContext ctx, int streamId, long errorCode,
Promise<Void> promise) {
public Future<Void> resetStream(final ChannelHandlerContext ctx, int streamId, long errorCode) {
final Http2Stream stream = connection().stream(streamId);
if (stream == null) {
return resetUnknownStream(ctx, streamId, errorCode, promise);
return resetUnknownStream(ctx, streamId, errorCode);
}
return resetStream(ctx, stream, errorCode, promise);
return resetStream(ctx, stream, errorCode);
}
private Future<Void> resetStream(final ChannelHandlerContext ctx, final Http2Stream stream,
long errorCode, Promise<Void> promise) {
long errorCode) {
if (stream.isResetSent()) {
// Don't write a RST_STREAM frame if we have already written one.
return promise.setSuccess(null);
return ctx.newSucceededFuture();
}
// Synchronously set the resetSent flag to prevent any subsequent calls
// from resulting in multiple reset frames being sent.
@ -747,9 +745,9 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
// https://tools.ietf.org/html/rfc7540#section-6.4.
if (stream.state() == IDLE ||
connection().local().created(stream) && !stream.isHeadersSent() && !stream.isPushPromiseSent()) {
future = promise.setSuccess(null);
future = ctx.newSucceededFuture();
} else {
future = frameWriter().writeRstStream(ctx, stream.id(), errorCode, promise);
future = frameWriter().writeRstStream(ctx, stream.id(), errorCode);
}
if (future.isDone()) {
processRstStreamWriteResult(ctx, stream, future);
@ -762,24 +760,22 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
@Override
public Future<Void> goAway(final ChannelHandlerContext ctx, final int lastStreamId, final long errorCode,
final ByteBuf debugData, Promise<Void> promise) {
final ByteBuf debugData) {
final Http2Connection connection = connection();
try {
if (!connection.goAwaySent(lastStreamId, errorCode, debugData)) {
debugData.release();
promise.trySuccess(null);
return promise;
return ctx.newSucceededFuture();
}
} catch (Throwable cause) {
debugData.release();
promise.tryFailure(cause);
return promise;
return ctx.newFailedFuture(cause);
}
// 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();
Future<Void> future = frameWriter().writeGoAway(ctx, lastStreamId, errorCode, debugData, promise);
Future<Void> future = frameWriter().writeGoAway(ctx, lastStreamId, errorCode, debugData);
if (future.isDone()) {
processGoAwayWriteResult(ctx, lastStreamId, errorCode, debugData, future);
@ -815,7 +811,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
* Close the remote endpoint with with a {@code GO_AWAY} frame. Does <strong>not</strong> flush
* immediately, this is the responsibility of the caller.
*/
private Future<Void> goAway(ChannelHandlerContext ctx, Http2Exception cause, Promise<Void> promise) {
private Future<Void> goAway(ChannelHandlerContext ctx, Http2Exception cause) {
long errorCode = cause != null ? cause.error().code() : NO_ERROR.code();
int lastKnownStream;
if (cause != null && cause.shutdownHint() == Http2Exception.ShutdownHint.HARD_SHUTDOWN) {
@ -827,7 +823,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
} else {
lastKnownStream = connection().remote().lastStreamCreated();
}
return goAway(ctx, lastKnownStream, errorCode, Http2CodecUtil.toByteBuf(ctx, cause), promise);
return goAway(ctx, lastKnownStream, errorCode, Http2CodecUtil.toByteBuf(ctx, cause));
}
@SuppressWarnings("unchecked")

View File

@ -17,7 +17,6 @@ package io.netty.handler.codec.http2;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
@ -49,38 +48,41 @@ final class Http2ControlFrameLimitEncoder extends DecoratingHttp2ConnectionEncod
}
@Override
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx, Promise<Void> promise) {
Promise<Void> newPromise = handleOutstandingControlFrames(ctx, promise);
if (newPromise == null) {
return promise;
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx) {
FutureListener<Void> listener = handleOutstandingControlFrames(ctx);
Future<Void> f = super.writeSettingsAck(ctx);
if (listener != null) {
f.addListener(listener);
}
return super.writeSettingsAck(ctx, newPromise);
return f;
}
@Override
public Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data, Promise<Void> promise) {
public Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data) {
// Only apply the limit to ping acks.
if (ack) {
Promise<Void> newPromise = handleOutstandingControlFrames(ctx, promise);
if (newPromise == null) {
return promise;
FutureListener<Void> listener = handleOutstandingControlFrames(ctx);
Future<Void> f = super.writePing(ctx, ack, data);
if (listener != null) {
f.addListener(listener);
}
return super.writePing(ctx, ack, data, newPromise);
return f;
}
return super.writePing(ctx, ack, data, promise);
return super.writePing(ctx, ack, data);
}
@Override
public Future<Void> writeRstStream(
ChannelHandlerContext ctx, int streamId, long errorCode, Promise<Void> promise) {
Promise<Void> newPromise = handleOutstandingControlFrames(ctx, promise);
if (newPromise == null) {
return promise;
ChannelHandlerContext ctx, int streamId, long errorCode) {
FutureListener<Void> listener = handleOutstandingControlFrames(ctx);
Future<Void> f = super.writeRstStream(ctx, streamId, errorCode);
if (listener != null) {
f.addListener(listener);
}
return super.writeRstStream(ctx, streamId, errorCode, newPromise);
return f;
}
private Promise<Void> handleOutstandingControlFrames(ChannelHandlerContext ctx, Promise<Void> promise) {
private FutureListener<Void> handleOutstandingControlFrames(ChannelHandlerContext ctx) {
if (!limitReached) {
if (outstandingControlFrames == maxOutstandingControlFrames) {
// Let's try to flush once as we may be able to flush some of the control frames.
@ -96,13 +98,14 @@ final class Http2ControlFrameLimitEncoder extends DecoratingHttp2ConnectionEncod
// First notify the Http2LifecycleManager and then close the connection.
lifecycleManager.onError(ctx, true, exception);
ctx.close();
return null;
}
outstandingControlFrames++;
// We did not reach the limit yet, add the listener to decrement the number of outstanding control frames
// once the promise was completed
promise.addListener(outstandingControlFramesListener);
return outstandingControlFramesListener;
}
return promise;
return null;
}
}

View File

@ -37,9 +37,8 @@ public interface Http2DataWriter {
* A 256 byte padding is encoded as the pad length field with value 255 and 255 padding bytes
* appended to the end of the frame.
* @param endStream indicates if this is the last frame to be sent for the stream.
* @param promise the promise for the write.
* @return the future for the write.
*/
Future<Void> writeData(ChannelHandlerContext ctx, int streamId,
ByteBuf data, int padding, boolean endStream, Promise<Void> promise);
ByteBuf data, int padding, boolean endStream);
}

View File

@ -282,13 +282,12 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
*/
@Override
public Future<Void> write(ChannelHandlerContext ctx, Object msg) {
Promise<Void> promise = ctx.newPromise();
if (msg instanceof Http2DataFrame) {
Http2DataFrame dataFrame = (Http2DataFrame) msg;
encoder().writeData(ctx, dataFrame.stream().id(), dataFrame.content(),
dataFrame.padding(), dataFrame.isEndStream(), promise);
return encoder().writeData(ctx, dataFrame.stream().id(), dataFrame.content(),
dataFrame.padding(), dataFrame.isEndStream());
} else if (msg instanceof Http2HeadersFrame) {
writeHeadersFrame(ctx, (Http2HeadersFrame) msg, promise);
return writeHeadersFrame(ctx, (Http2HeadersFrame) msg);
} else if (msg instanceof Http2WindowUpdateFrame) {
Http2WindowUpdateFrame frame = (Http2WindowUpdateFrame) msg;
Http2FrameStream frameStream = frame.stream();
@ -300,9 +299,9 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
} else {
consumeBytes(frameStream.id(), frame.windowSizeIncrement());
}
promise.setSuccess(null);
return ctx.newSucceededFuture();
} catch (Throwable t) {
promise.setFailure(t);
return ctx.newFailedFuture(t);
}
} else if (msg instanceof Http2ResetFrame) {
Http2ResetFrame rstFrame = (Http2ResetFrame) msg;
@ -310,41 +309,40 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
// Only ever send a reset frame if stream may have existed before as otherwise we may send a RST on a
// stream in an invalid state and cause a connection error.
if (connection().streamMayHaveExisted(id)) {
encoder().writeRstStream(ctx, rstFrame.stream().id(), rstFrame.errorCode(), promise);
return encoder().writeRstStream(ctx, rstFrame.stream().id(), rstFrame.errorCode());
} else {
ReferenceCountUtil.release(rstFrame);
promise.setFailure(Http2Exception.streamError(
return ctx.newFailedFuture(Http2Exception.streamError(
rstFrame.stream().id(), Http2Error.PROTOCOL_ERROR, "Stream never existed"));
}
} else if (msg instanceof Http2PingFrame) {
Http2PingFrame frame = (Http2PingFrame) msg;
encoder().writePing(ctx, frame.ack(), frame.content(), promise);
return encoder().writePing(ctx, frame.ack(), frame.content());
} else if (msg instanceof Http2SettingsFrame) {
encoder().writeSettings(ctx, ((Http2SettingsFrame) msg).settings(), promise);
return encoder().writeSettings(ctx, ((Http2SettingsFrame) msg).settings());
} else if (msg instanceof Http2SettingsAckFrame) {
// In the event of manual SETTINGS ACK, it is assumed the encoder will apply the earliest received but not
// yet ACKed settings.
encoder().writeSettingsAck(ctx, promise);
return encoder().writeSettingsAck(ctx);
} else if (msg instanceof Http2GoAwayFrame) {
writeGoAwayFrame(ctx, (Http2GoAwayFrame) msg, promise);
return writeGoAwayFrame(ctx, (Http2GoAwayFrame) msg);
} else if (msg instanceof Http2PushPromiseFrame) {
Http2PushPromiseFrame pushPromiseFrame = (Http2PushPromiseFrame) msg;
writePushPromise(ctx, pushPromiseFrame, promise);
return writePushPromise(ctx, pushPromiseFrame);
} else if (msg instanceof Http2PriorityFrame) {
Http2PriorityFrame priorityFrame = (Http2PriorityFrame) msg;
encoder().writePriority(ctx, priorityFrame.stream().id(), priorityFrame.streamDependency(),
priorityFrame.weight(), priorityFrame.exclusive(), promise);
return encoder().writePriority(ctx, priorityFrame.stream().id(), priorityFrame.streamDependency(),
priorityFrame.weight(), priorityFrame.exclusive());
} else if (msg instanceof Http2UnknownFrame) {
Http2UnknownFrame unknownFrame = (Http2UnknownFrame) msg;
encoder().writeFrame(ctx, unknownFrame.frameType(), unknownFrame.stream().id(),
unknownFrame.flags(), unknownFrame.content(), promise);
return encoder().writeFrame(ctx, unknownFrame.frameType(), unknownFrame.stream().id(),
unknownFrame.flags(), unknownFrame.content());
} else if (!(msg instanceof Http2Frame)) {
ctx.write(msg).cascadeTo(promise);
return ctx.write(msg);
} else {
ReferenceCountUtil.release(msg);
promise.setFailure(new UnsupportedMessageTypeException(msg));
return ctx.newFailedFuture(new UnsupportedMessageTypeException(msg));
}
return promise;
}
private void increaseInitialConnectionWindow(int deltaBytes) throws Http2Exception {
@ -366,10 +364,10 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
return connection().local().flowController().consumeBytes(stream, bytes);
}
private void writeGoAwayFrame(ChannelHandlerContext ctx, Http2GoAwayFrame frame, Promise<Void> promise) {
private Future<Void> writeGoAwayFrame(ChannelHandlerContext ctx, Http2GoAwayFrame frame) {
if (frame.lastStreamId() > -1) {
frame.release();
throw new IllegalArgumentException("Last stream id must not be set on GOAWAY frame");
return ctx.newFailedFuture(new IllegalArgumentException("Last stream id must not be set on GOAWAY frame"));
}
int lastStreamCreated = connection().remote().lastStreamCreated();
@ -378,66 +376,72 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
if (lastStreamId > Integer.MAX_VALUE) {
lastStreamId = Integer.MAX_VALUE;
}
goAway(ctx, (int) lastStreamId, frame.errorCode(), frame.content(), promise);
return goAway(ctx, (int) lastStreamId, frame.errorCode(), frame.content());
}
private void writeHeadersFrame(final ChannelHandlerContext ctx, Http2HeadersFrame headersFrame,
final Promise<Void> promise) {
private Future<Void> writeHeadersFrame(final ChannelHandlerContext ctx, Http2HeadersFrame headersFrame) {
if (isStreamIdValid(headersFrame.stream().id())) {
encoder().writeHeaders(ctx, headersFrame.stream().id(), headersFrame.headers(), headersFrame.padding(),
headersFrame.isEndStream(), promise);
} else if (initializeNewStream(ctx, (DefaultHttp2FrameStream) headersFrame.stream(), promise)) {
final int streamId = headersFrame.stream().id();
return encoder().writeHeaders(ctx, headersFrame.stream().id(), headersFrame.headers(),
headersFrame.padding(), headersFrame.isEndStream());
} else {
Future<Void> future = initializeNewStream(ctx, (DefaultHttp2FrameStream) headersFrame.stream());
if (future == null) {
final int streamId = headersFrame.stream().id();
encoder().writeHeaders(ctx, streamId, headersFrame.headers(), headersFrame.padding(),
headersFrame.isEndStream(), promise);
future = encoder().writeHeaders(ctx, streamId, headersFrame.headers(), headersFrame.padding(),
headersFrame.isEndStream());
if (!promise.isDone()) {
numBufferedStreams++;
// Clean up the stream being initialized if writing the headers fails and also
// decrement the number of buffered streams.
promise.addListener(channelFuture -> {
numBufferedStreams--;
if (!future.isDone()) {
numBufferedStreams++;
// Clean up the stream being initialized if writing the headers fails and also
// decrement the number of buffered streams.
future.addListener(channelFuture -> {
numBufferedStreams--;
handleHeaderFuture(channelFuture, streamId);
});
} else {
handleHeaderFuture(promise, streamId);
handleHeaderFuture(channelFuture, streamId);
});
} else {
handleHeaderFuture(future, streamId);
}
}
return future;
}
}
private void writePushPromise(final ChannelHandlerContext ctx, Http2PushPromiseFrame pushPromiseFrame,
final Promise<Void> promise) {
private Future<Void> writePushPromise(final ChannelHandlerContext ctx, Http2PushPromiseFrame pushPromiseFrame) {
if (isStreamIdValid(pushPromiseFrame.pushStream().id())) {
encoder().writePushPromise(ctx, pushPromiseFrame.stream().id(), pushPromiseFrame.pushStream().id(),
pushPromiseFrame.http2Headers(), pushPromiseFrame.padding(), promise);
} else if (initializeNewStream(ctx, (DefaultHttp2FrameStream) pushPromiseFrame.pushStream(), promise)) {
final int streamId = pushPromiseFrame.stream().id();
encoder().writePushPromise(ctx, streamId, pushPromiseFrame.pushStream().id(),
pushPromiseFrame.http2Headers(), pushPromiseFrame.padding(), promise);
return encoder().writePushPromise(ctx, pushPromiseFrame.stream().id(), pushPromiseFrame.pushStream().id(),
pushPromiseFrame.http2Headers(), pushPromiseFrame.padding());
} else {
Future<Void> future = initializeNewStream(ctx, (DefaultHttp2FrameStream) pushPromiseFrame.pushStream());
if (future == null) {
final int streamId = pushPromiseFrame.stream().id();
future = encoder().writePushPromise(ctx, streamId, pushPromiseFrame.pushStream().id(),
pushPromiseFrame.http2Headers(), pushPromiseFrame.padding());
if (promise.isDone()) {
handleHeaderFuture(promise, streamId);
} else {
numBufferedStreams++;
// Clean up the stream being initialized if writing the headers fails and also
// decrement the number of buffered streams.
promise.addListener(f -> {
numBufferedStreams--;
handleHeaderFuture(f, streamId);
});
if (future.isDone()) {
handleHeaderFuture(future, streamId);
} else {
numBufferedStreams++;
// Clean up the stream being initialized if writing the headers fails and also
// decrement the number of buffered streams.
future.addListener(f -> {
numBufferedStreams--;
handleHeaderFuture(f, streamId);
});
}
return future;
}
return future;
}
}
private boolean initializeNewStream(ChannelHandlerContext ctx, DefaultHttp2FrameStream http2FrameStream,
Promise<Void> promise) {
private Future<Void> initializeNewStream(ChannelHandlerContext ctx, DefaultHttp2FrameStream http2FrameStream) {
final Http2Connection connection = connection();
final int streamId = connection.local().incrementAndGetNextStreamId();
if (streamId < 0) {
promise.setFailure(new Http2NoMoreStreamIdsException());
Future<Void> f = ctx.newFailedFuture(new Http2NoMoreStreamIdsException());
// Simulate a GOAWAY being received due to stream exhaustion on this connection. We use the maximum
// valid stream ID for the current peer.
@ -445,7 +449,7 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
Integer.MAX_VALUE - 1, NO_ERROR.code(),
writeAscii(ctx.alloc(), "Stream IDs exhausted on local stream creation")));
return false;
return f;
}
http2FrameStream.id = streamId;
@ -458,7 +462,7 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
// We should not re-use ids.
assert old == null;
return true;
return null;
}
private void handleHeaderFuture(Future<?> channelFuture, int streamId) {

View File

@ -18,7 +18,6 @@ package io.netty.handler.codec.http2;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.UnstableApi;
import java.io.Closeable;
@ -54,7 +53,6 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* @param padding additional bytes that should be added to obscure the true content size. Must be between 0 and
* 256 (inclusive).
* @param endStream indicates if this is the last frame to be sent for the stream.
* @param promise the promise for the write.
* @return the future for the write.
* <a href="https://tools.ietf.org/html/rfc7540#section-10.5.1">Section 10.5.1</a> states the following:
* <pre>
@ -65,7 +63,7 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* If this call has <strong>NOT</strong> modified the HPACK header state you are free to throw a stream error.
*/
Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int padding, boolean endStream, Promise<Void> promise);
int padding, boolean endStream);
/**
* Writes a HEADERS frame with priority specified to the remote endpoint.
@ -80,7 +78,6 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* @param padding additional bytes that should be added to obscure the true content size. Must be between 0 and
* 256 (inclusive).
* @param endStream indicates if this is the last frame to be sent for the stream.
* @param promise the promise for the write.
* @return the future for the write.
* <a href="https://tools.ietf.org/html/rfc7540#section-10.5.1">Section 10.5.1</a> states the following:
* <pre>
@ -91,8 +88,7 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* If this call has <strong>NOT</strong> modified the HPACK header state you are free to throw a stream error.
*/
Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, boolean endStream,
Promise<Void> promise);
int streamDependency, short weight, boolean exclusive, int padding, boolean endStream);
/**
* Writes a PRIORITY frame to the remote endpoint.
@ -103,11 +99,10 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* depend on the connection.
* @param weight the weight for this stream.
* @param exclusive whether this stream should be the exclusive dependant of its parent.
* @param promise the promise for the write.
* @return the future for the write.
*/
Future<Void> writePriority(ChannelHandlerContext ctx, int streamId, int streamDependency,
short weight, boolean exclusive, Promise<Void> promise);
short weight, boolean exclusive);
/**
* Writes a RST_STREAM frame to the remote endpoint.
@ -115,31 +110,26 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* @param ctx the context to use for writing.
* @param streamId the stream for which to send the frame.
* @param errorCode the error code indicating the nature of the failure.
* @param promise the promise for the write.
* @return the future for the write.
*/
Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode,
Promise<Void> promise);
Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode);
/**
* Writes a SETTINGS frame to the remote endpoint.
*
* @param ctx the context to use for writing.
* @param settings the settings to be sent.
* @param promise the promise for the write.
* @return the future for the write.
*/
Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings,
Promise<Void> promise);
Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings);
/**
* Writes a SETTINGS acknowledgment to the remote endpoint.
*
* @param ctx the context to use for writing.
* @param promise the promise for the write.
* @return the future for the write.
*/
Future<Void> writeSettingsAck(ChannelHandlerContext ctx, Promise<Void> promise);
Future<Void> writeSettingsAck(ChannelHandlerContext ctx);
/**
* Writes a PING frame to the remote endpoint.
@ -148,11 +138,9 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* @param ack indicates whether this is an ack of a PING frame previously received from the
* remote endpoint.
* @param data the payload of the frame.
* @param promise the promise for the write.
* @return the future for the write.
*/
Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data,
Promise<Void> promise);
Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data);
/**
* Writes a PUSH_PROMISE frame to the remote endpoint.
@ -163,7 +151,6 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* @param headers the headers to be sent.
* @param padding additional bytes that should be added to obscure the true content size. Must be between 0 and
* 256 (inclusive).
* @param promise the promise for the write.
* @return the future for the write.
* <a href="https://tools.ietf.org/html/rfc7540#section-10.5.1">Section 10.5.1</a> states the following:
* <pre>
@ -174,7 +161,7 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* If this call has <strong>NOT</strong> modified the HPACK header state you are free to throw a stream error.
*/
Future<Void> writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
Http2Headers headers, int padding, Promise<Void> promise);
Http2Headers headers, int padding);
/**
* Writes a GO_AWAY frame to the remote endpoint.
@ -183,11 +170,10 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* @param lastStreamId the last known stream of this endpoint.
* @param errorCode the error code, if the connection was abnormally terminated.
* @param debugData application-defined debug data. This will be released by this method.
* @param promise the promise for the write.
* @return the future for the write.
*/
Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
ByteBuf debugData, Promise<Void> promise);
ByteBuf debugData);
/**
* Writes a WINDOW_UPDATE frame to the remote endpoint.
@ -196,11 +182,10 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* @param streamId the stream for which to send the frame.
* @param windowSizeIncrement the number of bytes by which the local inbound flow control window
* is increasing.
* @param promise the promise for the write.
* @return the future for the write.
*/
Future<Void> writeWindowUpdate(ChannelHandlerContext ctx, int streamId,
int windowSizeIncrement, Promise<Void> promise);
int windowSizeIncrement);
/**
* Generic write method for any HTTP/2 frame. This allows writing of non-standard frames.
@ -210,11 +195,10 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* @param streamId the stream for which to send the frame.
* @param flags the flags to write for this frame.
* @param payload the payload to write for this frame. This will be released by this method.
* @param promise the promise for the write.
* @return the future for the write.
*/
Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
Http2Flags flags, ByteBuf payload, Promise<Void> promise);
Http2Flags flags, ByteBuf payload);
/**
* Get the configuration related elements for this {@link Http2FrameWriter}

View File

@ -59,13 +59,11 @@ public interface Http2LifecycleManager {
* @param ctx The context used for communication and buffer allocation if necessary.
* @param streamId The identifier of the stream to reset.
* @param errorCode Justification as to why this stream is being reset. See {@link Http2Error}.
* @param promise Used to indicate the return status of this operation.
* @return Will be considered successful when the connection and stream state has been updated, and a
* {@code RST_STREAM} frame has been sent to the peer. If the stream state has already been updated and a
* {@code RST_STREAM} frame has been sent then the return status may indicate success immediately.
*/
Future<Void> resetStream(ChannelHandlerContext ctx, int streamId, long errorCode,
Promise<Void> promise);
Future<Void> resetStream(ChannelHandlerContext ctx, int streamId, long errorCode);
/**
* Prevents the peer from creating streams and close the connection if {@code errorCode} is not
@ -78,13 +76,12 @@ public interface Http2LifecycleManager {
* @param lastStreamId The last stream that the local endpoint is claiming it will accept.
* @param errorCode The rational as to why the connection is being closed. See {@link Http2Error}.
* @param debugData For diagnostic purposes (carries no semantic value).
* @param promise Used to indicate the return status of this operation.
* @return Will be considered successful when the connection and stream state has been updated, and a
* {@code GO_AWAY} frame has been sent to the peer. If the stream state has already been updated and a
* {@code GO_AWAY} frame has been sent then the return status may indicate success immediately.
*/
Future<Void> goAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
ByteBuf debugData, Promise<Void> promise);
ByteBuf debugData);
/**
* Processes the given error.

View File

@ -18,7 +18,6 @@ package io.netty.handler.codec.http2;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.UnstableApi;
import static io.netty.handler.codec.http2.Http2FrameLogger.Direction.OUTBOUND;
@ -40,93 +39,92 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
@Override
public Future<Void> writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
int padding, boolean endStream, Promise<Void> promise) {
int padding, boolean endStream) {
logger.logData(OUTBOUND, ctx, streamId, data, padding, endStream);
return writer.writeData(ctx, streamId, data, padding, endStream, promise);
return writer.writeData(ctx, streamId, data, padding, endStream);
}
@Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int padding, boolean endStream, Promise<Void> promise) {
Http2Headers headers, int padding, boolean endStream) {
logger.logHeaders(OUTBOUND, ctx, streamId, headers, padding, endStream);
return writer.writeHeaders(ctx, streamId, headers, padding, endStream, promise);
return writer.writeHeaders(ctx, streamId, headers, padding, endStream);
}
@Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int streamDependency, short weight, boolean exclusive,
int padding, boolean endStream, Promise<Void> promise) {
int padding, boolean endStream) {
logger.logHeaders(OUTBOUND, ctx, streamId, headers, streamDependency, weight, exclusive,
padding, endStream);
return writer.writeHeaders(ctx, streamId, headers, streamDependency, weight,
exclusive, padding, endStream, promise);
exclusive, padding, endStream);
}
@Override
public Future<Void> writePriority(ChannelHandlerContext ctx, int streamId,
int streamDependency, short weight, boolean exclusive, Promise<Void> promise) {
int streamDependency, short weight, boolean exclusive) {
logger.logPriority(OUTBOUND, ctx, streamId, streamDependency, weight, exclusive);
return writer.writePriority(ctx, streamId, streamDependency, weight, exclusive, promise);
return writer.writePriority(ctx, streamId, streamDependency, weight, exclusive);
}
@Override
public Future<Void> writeRstStream(ChannelHandlerContext ctx,
int streamId, long errorCode, Promise<Void> promise) {
int streamId, long errorCode) {
logger.logRstStream(OUTBOUND, ctx, streamId, errorCode);
return writer.writeRstStream(ctx, streamId, errorCode, promise);
return writer.writeRstStream(ctx, streamId, errorCode);
}
@Override
public Future<Void> writeSettings(ChannelHandlerContext ctx,
Http2Settings settings, Promise<Void> promise) {
Http2Settings settings) {
logger.logSettings(OUTBOUND, ctx, settings);
return writer.writeSettings(ctx, settings, promise);
return writer.writeSettings(ctx, settings);
}
@Override
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx, Promise<Void> promise) {
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx) {
logger.logSettingsAck(OUTBOUND, ctx);
return writer.writeSettingsAck(ctx, promise);
return writer.writeSettingsAck(ctx);
}
@Override
public Future<Void> writePing(ChannelHandlerContext ctx, boolean ack,
long data, Promise<Void> promise) {
long data) {
if (ack) {
logger.logPingAck(OUTBOUND, ctx, data);
} else {
logger.logPing(OUTBOUND, ctx, data);
}
return writer.writePing(ctx, ack, data, promise);
return writer.writePing(ctx, ack, data);
}
@Override
public Future<Void> writePushPromise(ChannelHandlerContext ctx, int streamId,
int promisedStreamId, Http2Headers headers, int padding,
Promise<Void> promise) {
int promisedStreamId, Http2Headers headers, int padding) {
logger.logPushPromise(OUTBOUND, ctx, streamId, promisedStreamId, headers, padding);
return writer.writePushPromise(ctx, streamId, promisedStreamId, headers, padding, promise);
return writer.writePushPromise(ctx, streamId, promisedStreamId, headers, padding);
}
@Override
public Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
ByteBuf debugData, Promise<Void> promise) {
ByteBuf debugData) {
logger.logGoAway(OUTBOUND, ctx, lastStreamId, errorCode, debugData);
return writer.writeGoAway(ctx, lastStreamId, errorCode, debugData, promise);
return writer.writeGoAway(ctx, lastStreamId, errorCode, debugData);
}
@Override
public Future<Void> writeWindowUpdate(ChannelHandlerContext ctx,
int streamId, int windowSizeIncrement, Promise<Void> promise) {
int streamId, int windowSizeIncrement) {
logger.logWindowsUpdate(OUTBOUND, ctx, streamId, windowSizeIncrement);
return writer.writeWindowUpdate(ctx, streamId, windowSizeIncrement, promise);
return writer.writeWindowUpdate(ctx, streamId, windowSizeIncrement);
}
@Override
public Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
Http2Flags flags, ByteBuf payload, Promise<Void> promise) {
Http2Flags flags, ByteBuf payload) {
logger.logUnknownFrame(OUTBOUND, ctx, frameType, streamId, flags, payload);
return writer.writeFrame(ctx, frameType, streamId, flags, payload, promise);
return writer.writeFrame(ctx, frameType, streamId, flags, payload);
}
@Override

View File

@ -106,7 +106,7 @@ public class HttpToHttp2ConnectionHandler extends Http2ConnectionHandler {
Http2Headers http2Headers = HttpConversionUtil.toHttp2Headers(httpMsg, validateHeaders);
endStream = msg instanceof FullHttpMessage && !((FullHttpMessage) msg).content().isReadable();
writeHeaders(ctx, encoder, currentStreamId, httpMsg.headers(), http2Headers,
endStream, promiseAggregator);
endStream).cascadeTo(promiseAggregator.newPromise());
}
if (!endStream && msg instanceof HttpContent) {
@ -125,12 +125,14 @@ public class HttpToHttp2ConnectionHandler extends Http2ConnectionHandler {
// Write the data
final ByteBuf content = ((HttpContent) msg).content();
endStream = isLastContent && trailers.isEmpty();
encoder.writeData(ctx, currentStreamId, content, 0, endStream, promiseAggregator.newPromise());
encoder.writeData(ctx, currentStreamId, content, 0, endStream)
.cascadeTo(promiseAggregator.newPromise());
release = false;
if (!trailers.isEmpty()) {
// Write trailing headers.
writeHeaders(ctx, encoder, currentStreamId, trailers, http2Trailers, true, promiseAggregator);
writeHeaders(ctx, encoder, currentStreamId, trailers, http2Trailers, true)
.cascadeTo(promiseAggregator.newPromise());
}
}
} catch (Throwable t) {
@ -145,14 +147,13 @@ public class HttpToHttp2ConnectionHandler extends Http2ConnectionHandler {
return promise;
}
private static void writeHeaders(ChannelHandlerContext ctx, Http2ConnectionEncoder encoder, int streamId,
HttpHeaders headers, Http2Headers http2Headers, boolean endStream,
SimpleChannelPromiseAggregator promiseAggregator) {
private static Future<Void> writeHeaders(ChannelHandlerContext ctx, Http2ConnectionEncoder encoder, int streamId,
HttpHeaders headers, Http2Headers http2Headers, boolean endStream) {
int dependencyId = headers.getInt(
HttpConversionUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 0);
short weight = headers.getShort(
HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT);
encoder.writeHeaders(ctx, streamId, http2Headers, dependencyId, weight, false,
0, endStream, promiseAggregator.newPromise());
return encoder.writeHeaders(ctx, streamId, http2Headers, dependencyId, weight, false,
0, endStream);
}
}

View File

@ -154,40 +154,41 @@ public class StreamBufferingEncoder extends DecoratingHttp2ConnectionEncoder {
@Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int padding, boolean endStream, Promise<Void> promise) {
int padding, boolean endStream) {
return writeHeaders(ctx, streamId, headers, 0, Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT,
false, padding, endStream, promise);
false, padding, endStream);
}
@Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive,
int padding, boolean endOfStream, Promise<Void> promise) {
int padding, boolean endOfStream) {
if (closed) {
return promise.setFailure(new Http2ChannelClosedException());
return ctx.newFailedFuture(new Http2ChannelClosedException());
}
if (isExistingStream(streamId) || canCreateStream()) {
return super.writeHeaders(ctx, streamId, headers, streamDependency, weight,
exclusive, padding, endOfStream, promise);
exclusive, padding, endOfStream);
}
if (goAwayDetail != null) {
return promise.setFailure(new Http2GoAwayException(goAwayDetail));
return ctx.newFailedFuture(new Http2GoAwayException(goAwayDetail));
}
PendingStream pendingStream = pendingStreams.get(streamId);
if (pendingStream == null) {
pendingStream = new PendingStream(ctx, streamId);
pendingStreams.put(streamId, pendingStream);
}
Promise<Void> promise = ctx.newPromise();
pendingStream.frames.add(new HeadersFrame(headers, streamDependency, weight, exclusive,
padding, endOfStream, promise));
return promise;
}
@Override
public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode,
Promise<Void> promise) {
public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode) {
if (isExistingStream(streamId)) {
return super.writeRstStream(ctx, streamId, errorCode, promise);
return super.writeRstStream(ctx, streamId, errorCode);
}
// Since the delegate doesn't know about any buffered streams we have to handle cancellation
// of the promises and releasing of the ByteBufs here.
@ -198,27 +199,27 @@ public class StreamBufferingEncoder extends DecoratingHttp2ConnectionEncoder {
// about the stream anymore and thus there is not point in failing the promises and invoking
// error handling routines.
stream.close(null);
promise.setSuccess(null);
return ctx.newSucceededFuture();
} else {
promise.setFailure(connectionError(PROTOCOL_ERROR, "Stream does not exist %d", streamId));
return ctx.newFailedFuture(connectionError(PROTOCOL_ERROR, "Stream does not exist %d", streamId));
}
return promise;
}
@Override
public Future<Void> writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
int padding, boolean endOfStream, Promise<Void> promise) {
int padding, boolean endOfStream) {
if (isExistingStream(streamId)) {
return super.writeData(ctx, streamId, data, padding, endOfStream, promise);
return super.writeData(ctx, streamId, data, padding, endOfStream);
}
PendingStream pendingStream = pendingStreams.get(streamId);
if (pendingStream != null) {
Promise<Void> promise = ctx.newPromise();
pendingStream.frames.add(new DataFrame(data, padding, endOfStream, promise));
return promise;
} else {
ReferenceCountUtil.safeRelease(data);
promise.setFailure(connectionError(PROTOCOL_ERROR, "Stream does not exist %d", streamId));
return ctx.newFailedFuture(connectionError(PROTOCOL_ERROR, "Stream does not exist %d", streamId));
}
return promise;
}
@Override
@ -352,7 +353,8 @@ public class StreamBufferingEncoder extends DecoratingHttp2ConnectionEncoder {
@Override
void send(ChannelHandlerContext ctx, int streamId) {
writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endOfStream, promise);
writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endOfStream)
.cascadeTo(promise);
}
}
@ -376,7 +378,7 @@ public class StreamBufferingEncoder extends DecoratingHttp2ConnectionEncoder {
@Override
void send(ChannelHandlerContext ctx, int streamId) {
writeData(ctx, streamId, data, padding, endOfStream, promise);
writeData(ctx, streamId, data, padding, endOfStream).cascadeTo(promise);
}
}
}

View File

@ -139,7 +139,7 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, true, newPromiseClient());
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, true);
clientHandler.flush(ctxClient());
});
awaitServer();
@ -157,8 +157,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient());
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient());
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient());
});
awaitServer();
@ -178,8 +178,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient());
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient());
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient());
});
awaitServer();
@ -201,9 +201,9 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient());
clientEncoder.writeData(ctxClient(), 3, data1.retain(), 0, false, newPromiseClient());
clientEncoder.writeData(ctxClient(), 3, data2.retain(), 0, true, newPromiseClient());
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data1.retain(), 0, false);
clientEncoder.writeData(ctxClient(), 3, data2.retain(), 0, true);
clientHandler.flush(ctxClient());
});
awaitServer();
@ -224,8 +224,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.BR);
runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient());
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient());
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient());
});
awaitServer();
@ -245,8 +245,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.BR);
runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient());
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient());
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient());
});
awaitServer();
@ -266,8 +266,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.ZSTD);
runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient());
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient());
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient());
});
awaitServer();
@ -287,8 +287,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.ZSTD);
runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient());
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient());
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient());
});
awaitServer();
@ -310,8 +310,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.DEFLATE);
runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient());
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient());
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient());
});
awaitServer();

View File

@ -79,7 +79,6 @@ public class DefaultHttp2ConnectionDecoderTest {
private static final int STATE_RECV_TRAILERS = 1 << 1;
private Http2ConnectionDecoder decoder;
private Promise<Void> promise;
@Mock
private Http2Connection connection;
@ -130,7 +129,7 @@ public class DefaultHttp2ConnectionDecoderTest {
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
promise = new DefaultPromise<>(ImmediateEventExecutor.INSTANCE);
Promise<Void> promise = new DefaultPromise<>(ImmediateEventExecutor.INSTANCE);
final AtomicInteger headersReceivedState = new AtomicInteger();
when(channel.isActive()).thenReturn(true);
@ -209,7 +208,7 @@ public class DefaultHttp2ConnectionDecoderTest {
decode().onSettingsRead(ctx, new Http2Settings());
verify(listener).onSettingsRead(eq(ctx), eq(new Http2Settings()));
assertTrue(decoder.prefaceReceived());
verify(encoder).writeSettingsAck(eq(ctx), eq(promise));
verify(encoder).writeSettingsAck(eq(ctx));
// Simulate receiving the SETTINGS ACK for the initial settings.
decode().onSettingsAckRead(ctx);
@ -793,7 +792,7 @@ public class DefaultHttp2ConnectionDecoderTest {
@Test
public void pingReadShouldReplyWithAck() throws Exception {
decode().onPingRead(ctx, 0L);
verify(encoder).writePing(eq(ctx), eq(true), eq(0L), eq(promise));
verify(encoder).writePing(eq(ctx), eq(true), eq(0L));
verify(listener, never()).onPingAckRead(eq(ctx), any(long.class));
}

View File

@ -26,7 +26,6 @@ import io.netty.channel.ChannelPipeline;
import io.netty.channel.DefaultChannelConfig;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http2.Http2RemoteFlowController.FlowControlled;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
@ -126,25 +125,23 @@ public class DefaultHttp2ConnectionEncoderTest {
when(channel.unsafe()).thenReturn(unsafe);
ChannelConfig config = new DefaultChannelConfig(channel);
when(channel.config()).thenReturn(config);
doAnswer(in -> newPromise()
.setFailure((Throwable) in.getArgument(0))).when(channel).newFailedFuture(any(Throwable.class));
when(writer.configuration()).thenReturn(writerConfig);
when(writerConfig.frameSizePolicy()).thenReturn(frameSizePolicy);
when(frameSizePolicy.maxFrameSize()).thenReturn(64);
doAnswer((Answer<Future<Void>>) in -> ((Promise<Void>) in.getArguments()[2])
.setSuccess(null)).when(writer).writeSettings(eq(ctx), any(Http2Settings.class), any(Promise.class));
doAnswer((Answer<Future<Void>>) in -> ImmediateEventExecutor.INSTANCE.newSucceededFuture(null))
.when(writer).writeSettings(eq(ctx), any(Http2Settings.class));
doAnswer((Answer<Future<Void>>) in -> {
((ByteBuf) in.getArguments()[3]).release();
return ((Promise<Void>) in.getArguments()[4]).setSuccess(null);
}).when(writer).writeGoAway(eq(ctx), anyInt(), anyInt(), any(ByteBuf.class), any(Promise.class));
return ImmediateEventExecutor.INSTANCE.newSucceededFuture(null);
}).when(writer).writeGoAway(eq(ctx), anyInt(), anyInt(), any(ByteBuf.class));
writtenData = new ArrayList<>();
writtenPadding = new ArrayList<>();
when(writer.writeData(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean(),
any(Promise.class))).then((Answer<Future<Void>>) in -> {
when(writer.writeData(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean()))
.then((Answer<Future<Void>>) in -> {
// Make sure we only receive stream closure on the last frame and that void promises
// are used for all writes except the last one.
Promise<Void> promise = (Promise<Void>) in.getArguments()[5];
if (streamClosed) {
fail("Stream already closed");
} else {
@ -156,39 +153,41 @@ public class DefaultHttp2ConnectionEncoderTest {
// Release the buffer just as DefaultHttp2FrameWriter does
data.release();
// Let the promise succeed to trigger listeners.
return promise.setSuccess(null);
return ImmediateEventExecutor.INSTANCE.newSucceededFuture(null);
});
when(writer.writeHeaders(eq(ctx), anyInt(), any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(),
anyInt(), anyBoolean(), any(Promise.class)))
anyInt(), anyBoolean()))
.then((Answer<Future<Void>>) invocationOnMock -> {
Promise<Void> promise = invocationOnMock.getArgument(8);
if (streamClosed) {
fail("Stream already closed");
} else {
streamClosed = (Boolean) invocationOnMock.getArguments()[5];
}
return promise.setSuccess(null);
return ImmediateEventExecutor.INSTANCE.newSucceededFuture(null);
});
when(writer.writeHeaders(eq(ctx), anyInt(), any(Http2Headers.class),
anyInt(), anyBoolean(), any(Promise.class)))
anyInt(), anyBoolean()))
.then((Answer<Future<Void>>) invocationOnMock -> {
Promise<Void> promise = invocationOnMock.getArgument(5);
if (streamClosed) {
fail("Stream already closed");
} else {
streamClosed = invocationOnMock.getArgument(4);
}
return promise.setSuccess(null);
return ImmediateEventExecutor.INSTANCE.newSucceededFuture(null);
});
payloadCaptor = ArgumentCaptor.forClass(Http2RemoteFlowController.FlowControlled.class);
doNothing().when(remoteFlow).addFlowControlled(any(Http2Stream.class), payloadCaptor.capture());
when(ctx.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT);
when(ctx.channel()).thenReturn(channel);
doAnswer((Answer<Promise<Void>>) in -> newPromise()).when(ctx).newPromise();
doAnswer((Answer<Future<Void>>) in -> newSucceededFuture()).when(ctx).newSucceededFuture();
doAnswer((Answer<Promise<Void>>) in -> ImmediateEventExecutor.INSTANCE.newPromise()).when(ctx).newPromise();
doAnswer((Answer<Future<Void>>) in -> ImmediateEventExecutor.INSTANCE.newSucceededFuture(null))
.when(ctx).newSucceededFuture();
doAnswer((Answer<Future<Void>>) in -> ImmediateEventExecutor.INSTANCE.newFailedFuture(in.getArgument(0)))
.when(ctx).newFailedFuture(any(Throwable.class));
when(ctx.flush()).thenThrow(new AssertionFailedError("forbidden"));
when(channel.alloc()).thenReturn(PooledByteBufAllocator.DEFAULT);
doAnswer((Answer<Future<Void>>) in -> ImmediateEventExecutor.INSTANCE.newFailedFuture(in.getArgument(0)))
.when(channel).newFailedFuture(any(Throwable.class));
// Use a server-side connection so we can test server push.
connection = new DefaultHttp2Connection(true);
connection.remote().flowController(remoteFlow);
@ -210,8 +209,7 @@ public class DefaultHttp2ConnectionEncoderTest {
private void dataWriteShouldSignalThatFrameWasConsumedOnError0(boolean endOfStream) throws Exception {
createStream(STREAM_ID, false);
final ByteBuf data = dummyData();
Promise<Void> p = newPromise();
encoder.writeData(ctx, STREAM_ID, data, 0, endOfStream, p);
Future<Void> f = encoder.writeData(ctx, STREAM_ID, data, 0, endOfStream);
FlowControlled controlled = payloadCaptor.getValue();
assertEquals(8, controlled.size());
@ -224,21 +222,20 @@ public class DefaultHttp2ConnectionEncoderTest {
assertEquals(0, controlled.size());
assertEquals("abcd", writtenData.get(0));
assertEquals(0, data.refCnt());
assertSame(error, p.cause());
assertSame(error, f.cause());
}
@Test
public void dataWriteShouldSucceed() throws Exception {
createStream(STREAM_ID, false);
final ByteBuf data = dummyData();
Promise<Void> p = newPromise();
encoder.writeData(ctx, STREAM_ID, data, 0, true, p);
Future<Void> f = encoder.writeData(ctx, STREAM_ID, data, 0, true);
assertEquals(8, payloadCaptor.getValue().size());
payloadCaptor.getValue().write(ctx, 8);
assertEquals(0, payloadCaptor.getValue().size());
assertEquals("abcdefgh", writtenData.get(0));
assertEquals(0, data.refCnt());
assertTrue(p.isSuccess());
assertTrue(f.isSuccess());
}
@Test
@ -246,35 +243,33 @@ public class DefaultHttp2ConnectionEncoderTest {
createStream(STREAM_ID, false);
final ByteBuf data = dummyData().retain();
Promise<Void> promise1 = newPromise();
encoder.writeData(ctx, STREAM_ID, data, 0, true, promise1);
Promise<Void> promise2 = newPromise();
encoder.writeData(ctx, STREAM_ID, data, 0, true, promise2);
Future<Void> future1 = encoder.writeData(ctx, STREAM_ID, data, 0, true);
Future<Void> future2 = encoder.writeData(ctx, STREAM_ID, data, 0, true);
// Now merge the two payloads.
List<FlowControlled> capturedWrites = payloadCaptor.getAllValues();
FlowControlled mergedPayload = capturedWrites.get(0);
mergedPayload.merge(ctx, capturedWrites.get(1));
assertEquals(16, mergedPayload.size());
assertFalse(promise1.isDone());
assertFalse(promise2.isDone());
assertFalse(future1.isDone());
assertFalse(future2.isDone());
// Write the merged payloads and verify it was written correctly.
mergedPayload.write(ctx, 16);
assertEquals(0, mergedPayload.size());
assertEquals("abcdefghabcdefgh", writtenData.get(0));
assertEquals(0, data.refCnt());
assertTrue(promise1.isSuccess());
assertTrue(promise2.isSuccess());
assertTrue(future1.isSuccess());
assertTrue(future2.isSuccess());
}
@Test
public void dataFramesDontMergeWithHeaders() throws Exception {
createStream(STREAM_ID, false);
final ByteBuf data = dummyData().retain();
encoder.writeData(ctx, STREAM_ID, data, 0, false, newPromise());
encoder.writeData(ctx, STREAM_ID, data, 0, false);
when(remoteFlow.hasFlowControlled(any(Http2Stream.class))).thenReturn(true);
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true, newPromise());
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true);
List<FlowControlled> capturedWrites = payloadCaptor.getAllValues();
assertFalse(capturedWrites.get(0).merge(ctx, capturedWrites.get(1)));
}
@ -289,8 +284,7 @@ public class DefaultHttp2ConnectionEncoderTest {
private void assertSplitPaddingOnEmptyBuffer(ByteBuf data) throws Exception {
createStream(STREAM_ID, false);
when(frameSizePolicy.maxFrameSize()).thenReturn(5);
Promise<Void> p = newPromise();
encoder.writeData(ctx, STREAM_ID, data, 10, true, p);
Future<Void> f = encoder.writeData(ctx, STREAM_ID, data, 10, true);
assertEquals(10, payloadCaptor.getValue().size());
payloadCaptor.getValue().write(ctx, 10);
// writer was called 2 times
@ -298,18 +292,17 @@ public class DefaultHttp2ConnectionEncoderTest {
assertEquals("", writtenData.get(0));
assertEquals(10, (int) writtenPadding.get(0));
assertEquals(0, data.refCnt());
assertTrue(p.isSuccess());
assertTrue(f.isSuccess());
}
@Test
public void headersWriteForUnknownStreamShouldCreateStream() throws Exception {
writeAllFlowControlledFrames();
final int streamId = 6;
Promise<Void> promise = newPromise();
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise);
Future<Void> f = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
verify(writer).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0),
eq(false), eq(promise));
assertTrue(promise.isSuccess());
eq(false));
assertTrue(f.isSuccess());
}
@Test
@ -318,46 +311,41 @@ public class DefaultHttp2ConnectionEncoderTest {
Http2Stream parent = createStream(STREAM_ID, false);
reservePushStream(PUSH_STREAM_ID, parent);
Promise<Void> promise = newPromise();
encoder.writeHeaders(ctx, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false, promise);
encoder.writeHeaders(ctx, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false);
assertEquals(HALF_CLOSED_REMOTE, stream(PUSH_STREAM_ID).state());
verify(writer).writeHeaders(eq(ctx), eq(PUSH_STREAM_ID), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise));
eq(0), eq(false));
}
@Test
public void trailersDoNotEndStreamThrows() {
writeAllFlowControlledFrames();
final int streamId = 6;
Promise<Void> promise = newPromise();
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
Promise<Void> promise2 = newPromise();
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise2);
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
assertTrue(future.isDone());
assertFalse(future.isSuccess());
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise));
eq(0), eq(false));
}
@Test
public void trailersDoNotEndStreamWithDataThrows() {
writeAllFlowControlledFrames();
final int streamId = 6;
Promise<Void> promise = newPromise();
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
Http2Stream stream = connection.stream(streamId);
when(remoteFlow.hasFlowControlled(eq(stream))).thenReturn(true);
Promise<Void> promise2 = newPromise();
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise2);
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
assertTrue(future.isDone());
assertFalse(future.isSuccess());
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise));
eq(0), eq(false));
}
@Test
@ -373,20 +361,17 @@ public class DefaultHttp2ConnectionEncoderTest {
private void tooManyHeadersThrows(boolean eos) {
writeAllFlowControlledFrames();
final int streamId = 6;
Promise<Void> promise = newPromise();
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise);
Promise<Void> promise2 = newPromise();
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, true, promise2);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, true);
Promise<Void> promise3 = newPromise();
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos, promise3);
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos);
assertTrue(future.isDone());
assertFalse(future.isSuccess());
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise));
eq(0), eq(false));
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(true), eq(promise2));
eq(0), eq(true));
}
@Test
@ -414,23 +399,21 @@ public class DefaultHttp2ConnectionEncoderTest {
final int streamId = 6;
Http2Headers infoHeaders = informationalHeaders();
for (int i = 0; i < infoHeaderCount; ++i) {
encoder.writeHeaders(ctx, streamId, infoHeaders, 0, false, newPromise());
encoder.writeHeaders(ctx, streamId, infoHeaders, 0, false);
}
Promise<Void> promise2 = newPromise();
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise2);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
Promise<Void> promise3 = newPromise();
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos, promise3);
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos);
assertTrue(future.isDone());
assertEquals(eos, future.isSuccess());
verify(writer, times(infoHeaderCount)).writeHeaders(eq(ctx), eq(streamId), eq(infoHeaders),
eq(0), eq(false), any(Promise.class));
eq(0), eq(false));
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise2));
eq(0), eq(false));
if (eos) {
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(true), eq(promise3));
eq(0), eq(true));
}
}
@ -453,24 +436,21 @@ public class DefaultHttp2ConnectionEncoderTest {
private void tooManyHeadersWithDataThrows(boolean eos) {
writeAllFlowControlledFrames();
final int streamId = 6;
Promise<Void> promise = newPromise();
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
Http2Stream stream = connection.stream(streamId);
when(remoteFlow.hasFlowControlled(eq(stream))).thenReturn(true);
Promise<Void> promise2 = newPromise();
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, true, promise2);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, true);
Promise<Void> promise3 = newPromise();
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos, promise3);
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos);
assertTrue(future.isDone());
assertFalse(future.isSuccess());
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise));
eq(0), eq(false));
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(true), eq(promise2));
eq(0), eq(true));
}
@Test
@ -498,27 +478,25 @@ public class DefaultHttp2ConnectionEncoderTest {
final int streamId = 6;
Http2Headers infoHeaders = informationalHeaders();
for (int i = 0; i < infoHeaderCount; ++i) {
encoder.writeHeaders(ctx, streamId, infoHeaders, 0, false, newPromise());
encoder.writeHeaders(ctx, streamId, infoHeaders, 0, false);
}
Http2Stream stream = connection.stream(streamId);
when(remoteFlow.hasFlowControlled(eq(stream))).thenReturn(true);
Promise<Void> promise2 = newPromise();
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise2);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
Promise<Void> promise3 = newPromise();
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos, promise3);
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos);
assertTrue(future.isDone());
assertEquals(eos, future.isSuccess());
verify(writer, times(infoHeaderCount)).writeHeaders(eq(ctx), eq(streamId), eq(infoHeaders),
eq(0), eq(false), any(Promise.class));
eq(0), eq(false));
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise2));
eq(0), eq(false));
if (eos) {
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(true), eq(promise3));
eq(0), eq(true));
}
}
@ -526,8 +504,7 @@ public class DefaultHttp2ConnectionEncoderTest {
public void pushPromiseWriteAfterGoAwayReceivedShouldFail() throws Exception {
createStream(STREAM_ID, false);
goAwayReceived(0);
Future<Void> future = encoder.writePushPromise(ctx, STREAM_ID, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0,
newPromise());
Future<Void> future = encoder.writePushPromise(ctx, STREAM_ID, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0);
assertTrue(future.isDone());
assertFalse(future.isSuccess());
}
@ -535,42 +512,38 @@ public class DefaultHttp2ConnectionEncoderTest {
@Test
public void pushPromiseWriteShouldReserveStream() throws Exception {
createStream(STREAM_ID, false);
Promise<Void> promise = newPromise();
encoder.writePushPromise(ctx, STREAM_ID, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, promise);
encoder.writePushPromise(ctx, STREAM_ID, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0);
assertEquals(RESERVED_LOCAL, stream(PUSH_STREAM_ID).state());
verify(writer).writePushPromise(eq(ctx), eq(STREAM_ID), eq(PUSH_STREAM_ID),
eq(EmptyHttp2Headers.INSTANCE), eq(0), eq(promise));
eq(EmptyHttp2Headers.INSTANCE), eq(0));
}
@Test
public void priorityWriteAfterGoAwayShouldSucceed() throws Exception {
createStream(STREAM_ID, false);
goAwayReceived(Integer.MAX_VALUE);
Promise<Void> promise = newPromise();
encoder.writePriority(ctx, STREAM_ID, 0, (short) 255, true, promise);
verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(0), eq((short) 255), eq(true), eq(promise));
encoder.writePriority(ctx, STREAM_ID, 0, (short) 255, true);
verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(0), eq((short) 255), eq(true));
}
@Test
public void priorityWriteShouldSetPriorityForStream() throws Exception {
Promise<Void> promise = newPromise();
short weight = 255;
encoder.writePriority(ctx, STREAM_ID, 0, weight, true, promise);
encoder.writePriority(ctx, STREAM_ID, 0, weight, true);
// Verify that this did NOT create a stream object.
Http2Stream stream = stream(STREAM_ID);
assertNull(stream);
verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(0), eq((short) 255), eq(true), eq(promise));
verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(0), eq((short) 255), eq(true));
}
@Test
public void priorityWriteOnPreviouslyExistingStreamShouldSucceed() throws Exception {
createStream(STREAM_ID, false).close();
Promise<Void> promise = newPromise();
short weight = 255;
encoder.writePriority(ctx, STREAM_ID, 0, weight, true, promise);
verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(0), eq(weight), eq(true), eq(promise));
encoder.writePriority(ctx, STREAM_ID, 0, weight, true);
verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(0), eq(weight), eq(true));
}
@Test
@ -579,53 +552,47 @@ public class DefaultHttp2ConnectionEncoderTest {
createStream(STREAM_ID, false);
createStream(parentStreamId, false).close();
Promise<Void> promise = newPromise();
short weight = 255;
encoder.writePriority(ctx, STREAM_ID, parentStreamId, weight, true, promise);
verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(parentStreamId), eq(weight), eq(true), eq(promise));
encoder.writePriority(ctx, STREAM_ID, parentStreamId, weight, true);
verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(parentStreamId), eq(weight), eq(true));
}
@Test
public void rstStreamWriteForUnknownStreamShouldIgnore() throws Exception {
Promise<Void> promise = newPromise();
encoder.writeRstStream(ctx, 5, PROTOCOL_ERROR.code(), promise);
verify(writer, never()).writeRstStream(eq(ctx), anyInt(), anyLong(), eq(promise));
encoder.writeRstStream(ctx, 5, PROTOCOL_ERROR.code());
verify(writer, never()).writeRstStream(eq(ctx), anyInt(), anyLong());
}
@Test
public void rstStreamShouldCloseStream() throws Exception {
// Create the stream and send headers.
writeAllFlowControlledFrames();
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true, newPromise());
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true);
// Now verify that a stream reset is performed.
stream(STREAM_ID);
Promise<Void> promise = newPromise();
encoder.writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code(), promise);
verify(lifecycleManager).resetStream(eq(ctx), eq(STREAM_ID), anyLong(), eq(promise));
encoder.writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code());
verify(lifecycleManager).resetStream(eq(ctx), eq(STREAM_ID), anyLong());
}
@Test
public void pingWriteAfterGoAwayShouldSucceed() throws Exception {
Promise<Void> promise = newPromise();
goAwayReceived(0);
encoder.writePing(ctx, false, 0L, promise);
verify(writer).writePing(eq(ctx), eq(false), eq(0L), eq(promise));
encoder.writePing(ctx, false, 0L);
verify(writer).writePing(eq(ctx), eq(false), eq(0L));
}
@Test
public void pingWriteShouldSucceed() throws Exception {
Promise<Void> promise = newPromise();
encoder.writePing(ctx, false, 0L, promise);
verify(writer).writePing(eq(ctx), eq(false), eq(0L), eq(promise));
encoder.writePing(ctx, false, 0L);
verify(writer).writePing(eq(ctx), eq(false), eq(0L));
}
@Test
public void settingsWriteAfterGoAwayShouldSucceed() throws Exception {
goAwayReceived(0);
Promise<Void> promise = newPromise();
encoder.writeSettings(ctx, new Http2Settings(), promise);
verify(writer).writeSettings(eq(ctx), any(Http2Settings.class), eq(promise));
encoder.writeSettings(ctx, new Http2Settings());
verify(writer).writeSettings(eq(ctx), any(Http2Settings.class));
}
@Test
@ -635,9 +602,8 @@ public class DefaultHttp2ConnectionEncoderTest {
settings.maxConcurrentStreams(1000);
settings.headerTableSize(2000);
Promise<Void> promise = newPromise();
encoder.writeSettings(ctx, settings, promise);
verify(writer).writeSettings(eq(ctx), eq(settings), eq(promise));
encoder.writeSettings(ctx, settings);
verify(writer).writeSettings(eq(ctx), eq(settings));
}
@Test
@ -646,11 +612,10 @@ public class DefaultHttp2ConnectionEncoderTest {
Http2Stream stream = createStream(STREAM_ID, false);
ByteBuf data = dummyData();
Promise<Void> promise = newPromise();
encoder.writeData(ctx, STREAM_ID, data.retain(), 0, true, promise);
assertTrue(promise.isSuccess());
Future<Void> f = encoder.writeData(ctx, STREAM_ID, data.retain(), 0, true);
assertTrue(f.isSuccess());
verify(remoteFlow).addFlowControlled(eq(stream), any(FlowControlled.class));
verify(lifecycleManager).closeStreamLocal(stream, promise);
verify(lifecycleManager).closeStreamLocal(eq(stream), eq(f));
assertEquals(data.toString(UTF_8), writtenData.get(0));
data.release();
}
@ -659,11 +624,10 @@ public class DefaultHttp2ConnectionEncoderTest {
public void headersWriteShouldHalfCloseStream() throws Exception {
writeAllFlowControlledFrames();
createStream(STREAM_ID, false);
Promise<Void> promise = newPromise();
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true, promise);
Future<Void> f = encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true);
assertTrue(promise.isSuccess());
verify(lifecycleManager).closeStreamLocal(eq(stream(STREAM_ID)), eq(promise));
assertTrue(f.isSuccess());
verify(lifecycleManager).closeStreamLocal(eq(stream(STREAM_ID)), eq(f));
}
@Test
@ -671,64 +635,54 @@ public class DefaultHttp2ConnectionEncoderTest {
writeAllFlowControlledFrames();
Http2Stream parent = createStream(STREAM_ID, false);
Http2Stream stream = reservePushStream(PUSH_STREAM_ID, parent);
Promise<Void> promise = newPromise();
encoder.writeHeaders(ctx, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true, promise);
Future<Void> f = encoder.writeHeaders(ctx, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true);
assertEquals(HALF_CLOSED_REMOTE, stream.state());
assertTrue(promise.isSuccess());
verify(lifecycleManager).closeStreamLocal(eq(stream), eq(promise));
assertTrue(f.isSuccess());
verify(lifecycleManager).closeStreamLocal(eq(stream), eq(f));
}
@Test
public void headersWriteShouldHalfCloseAfterOnErrorForPreCreatedStream() throws Exception {
final Promise<Void> promise = newPromise();
final Throwable ex = new RuntimeException();
// Fake an encoding error, like HPACK's HeaderListSizeException
when(writer.writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE), eq(0), eq(true), eq(promise)))
.thenAnswer((Answer<Future<Void>>) invocation -> {
promise.setFailure(ex);
return promise;
});
when(writer.writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE), eq(0), eq(true)))
.thenReturn(ImmediateEventExecutor.INSTANCE.newFailedFuture(ex));
writeAllFlowControlledFrames();
Http2Stream stream = createStream(STREAM_ID, false);
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true, promise);
Future<Void> f = encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true);
assertTrue(promise.isDone());
assertFalse(promise.isSuccess());
assertTrue(f.isDone());
assertFalse(f.isSuccess());
assertFalse(stream.isHeadersSent());
InOrder inOrder = inOrder(lifecycleManager);
inOrder.verify(lifecycleManager).onError(eq(ctx), eq(true), eq(ex));
inOrder.verify(lifecycleManager).closeStreamLocal(eq(stream(STREAM_ID)), eq(promise));
inOrder.verify(lifecycleManager).closeStreamLocal(eq(stream(STREAM_ID)), eq(f));
}
@Test
public void headersWriteShouldHalfCloseAfterOnErrorForImplicitlyCreatedStream() throws Exception {
final Promise<Void> promise = newPromise();
final Throwable ex = new RuntimeException();
// Fake an encoding error, like HPACK's HeaderListSizeException
when(writer.writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE), eq(0), eq(true), eq(promise)))
.thenAnswer((Answer<Future<Void>>) invocation -> {
promise.setFailure(ex);
return promise;
});
when(writer.writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE), eq(0), eq(true)))
.thenReturn(ImmediateEventExecutor.INSTANCE.newFailedFuture(ex));
writeAllFlowControlledFrames();
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true, promise);
Future<Void> f = encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true);
assertTrue(promise.isDone());
assertFalse(promise.isSuccess());
assertTrue(f.isDone());
assertFalse(f.isSuccess());
assertFalse(stream(STREAM_ID).isHeadersSent());
InOrder inOrder = inOrder(lifecycleManager);
inOrder.verify(lifecycleManager).onError(eq(ctx), eq(true), eq(ex));
inOrder.verify(lifecycleManager).closeStreamLocal(eq(stream(STREAM_ID)), eq(promise));
inOrder.verify(lifecycleManager).closeStreamLocal(eq(stream(STREAM_ID)), eq(f));
}
@Test
public void encoderDelegatesGoAwayToLifeCycleManager() {
Promise<Void> promise = newPromise();
encoder.writeGoAway(ctx, STREAM_ID, Http2Error.INTERNAL_ERROR.code(), null, promise);
encoder.writeGoAway(ctx, STREAM_ID, Http2Error.INTERNAL_ERROR.code(), null);
verify(lifecycleManager).goAway(eq(ctx), eq(STREAM_ID), eq(Http2Error.INTERNAL_ERROR.code()),
eq((ByteBuf) null), eq(promise));
eq((ByteBuf) null));
verifyNoMoreInteractions(writer);
}
@ -736,11 +690,10 @@ public class DefaultHttp2ConnectionEncoderTest {
public void dataWriteToClosedStreamShouldFail() throws Exception {
createStream(STREAM_ID, false).close();
ByteBuf data = mock(ByteBuf.class);
Promise<Void> promise = newPromise();
encoder.writeData(ctx, STREAM_ID, data, 0, false, promise);
assertTrue(promise.isDone());
assertFalse(promise.isSuccess());
assertThat(promise.cause(), instanceOf(IllegalArgumentException.class));
Future<Void> f = encoder.writeData(ctx, STREAM_ID, data, 0, false);
assertTrue(f.isDone());
assertFalse(f.isSuccess());
assertThat(f.cause(), instanceOf(IllegalArgumentException.class));
verify(data).release();
}
@ -748,11 +701,10 @@ public class DefaultHttp2ConnectionEncoderTest {
public void dataWriteToHalfClosedLocalStreamShouldFail() throws Exception {
createStream(STREAM_ID, true);
ByteBuf data = mock(ByteBuf.class);
Promise<Void> promise = newPromise();
encoder.writeData(ctx, STREAM_ID, data, 0, false, promise);
assertTrue(promise.isDone());
assertFalse(promise.isSuccess());
assertThat(promise.cause(), instanceOf(IllegalStateException.class));
Future<Void> f = encoder.writeData(ctx, STREAM_ID, data, 0, false);
assertTrue(f.isDone());
assertFalse(f.isSuccess());
assertThat(f.cause(), instanceOf(IllegalStateException.class));
verify(data).release();
}
@ -761,7 +713,7 @@ public class DefaultHttp2ConnectionEncoderTest {
Http2Stream stream = createStream(STREAM_ID, false);
connection.goAwaySent(0, 0, EMPTY_BUFFER);
ByteBuf data = mock(ByteBuf.class);
encoder.writeData(ctx, STREAM_ID, data, 0, false, newPromise());
encoder.writeData(ctx, STREAM_ID, data, 0, false);
verify(remoteFlow).addFlowControlled(eq(stream), any(FlowControlled.class));
}
@ -770,10 +722,9 @@ public class DefaultHttp2ConnectionEncoderTest {
writeAllFlowControlledFrames();
createStream(STREAM_ID, false);
goAwaySent(0);
Promise<Void> promise = newPromise();
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false, promise);
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false);
verify(writer).writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise));
eq(0), eq(false));
}
@Test
@ -781,7 +732,7 @@ public class DefaultHttp2ConnectionEncoderTest {
Http2Stream stream = createStream(STREAM_ID, false);
goAwayReceived(STREAM_ID);
ByteBuf data = mock(ByteBuf.class);
encoder.writeData(ctx, STREAM_ID, data, 0, false, newPromise());
encoder.writeData(ctx, STREAM_ID, data, 0, false);
verify(remoteFlow).addFlowControlled(eq(stream), any(FlowControlled.class));
}
@ -789,31 +740,28 @@ public class DefaultHttp2ConnectionEncoderTest {
public void canWriteHeaderFrameAfterGoAwayReceived() throws Http2Exception {
writeAllFlowControlledFrames();
goAwayReceived(STREAM_ID);
Promise<Void> promise = newPromise();
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false, promise);
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false);
verify(writer).writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise));
eq(0), eq(false));
}
@Test
public void headersWithNoPriority() {
writeAllFlowControlledFrames();
final int streamId = 6;
Promise<Void> promise = newPromise();
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
verify(writer).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise));
eq(0), eq(false));
}
@Test
public void headersWithPriority() {
writeAllFlowControlledFrames();
final int streamId = 6;
Promise<Void> promise = newPromise();
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 10, DEFAULT_PRIORITY_WEIGHT,
true, 1, false, promise);
true, 1, false);
verify(writer).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(10),
eq(DEFAULT_PRIORITY_WEIGHT), eq(true), eq(1), eq(false), eq(promise));
eq(DEFAULT_PRIORITY_WEIGHT), eq(true), eq(1), eq(false));
}
private void writeAllFlowControlledFrames() {
@ -845,14 +793,6 @@ public class DefaultHttp2ConnectionEncoderTest {
connection.goAwaySent(lastStreamId, 0, EMPTY_BUFFER);
}
private static Promise<Void> newPromise() {
return new DefaultPromise<>(ImmediateEventExecutor.INSTANCE);
}
private static Future<Void> newSucceededFuture() {
return newPromise().setSuccess(null);
}
private static ByteBuf dummyData() {
// The buffer is purposely 8 bytes so it will even work for a ping frame.
return wrappedBuffer("abcdefgh".getBytes(UTF_8));

View File

@ -22,8 +22,6 @@ import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.channel.local.LocalHandler;
import io.netty.handler.codec.http2.Http2Connection.Endpoint;
import io.netty.handler.codec.http2.Http2Stream.State;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@ -157,10 +155,9 @@ public class DefaultHttp2ConnectionTest {
final Promise<Void> promise = group.next().newPromise();
final CountDownLatch latch = new CountDownLatch(client.numActiveStreams());
client.forEachActiveStream(stream -> {
client.close(promise).addListener((FutureListener<Void>) future -> {
assertTrue(promise.isDone());
client.close(promise.addListener(future -> {
latch.countDown();
});
}));
return true;
});
assertTrue(latch.await(5, TimeUnit.SECONDS));
@ -186,10 +183,9 @@ public class DefaultHttp2ConnectionTest {
return true;
});
} catch (Http2Exception ignored) {
client.close(promise).addListener((FutureListener<Void>) future -> {
assertTrue(promise.isDone());
client.close(promise.addListener(future -> {
latch.countDown();
});
}));
}
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ -636,10 +632,9 @@ public class DefaultHttp2ConnectionTest {
private void testRemoveAllStreams() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
final Promise<Void> promise = group.next().newPromise();
client.close(promise).addListener((FutureListener<Void>) future -> {
assertTrue(promise.isDone());
client.close(promise.addListener(future -> {
latch.countDown();
});
}));
assertTrue(latch.await(5, TimeUnit.SECONDS));
}

View File

@ -20,10 +20,8 @@ import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -50,8 +48,6 @@ public class DefaultHttp2FrameWriterTest {
private ByteBuf expectedOutbound;
private Promise<Void> promise;
private Http2HeadersEncoder http2HeadersEncoder;
@Mock
@ -76,8 +72,6 @@ public class DefaultHttp2FrameWriterTest {
expectedOutbound = Unpooled.EMPTY_BUFFER;
promise = new DefaultPromise<>(ImmediateEventExecutor.INSTANCE);
Answer<Object> answer = var1 -> {
Object msg = var1.getArgument(0);
if (msg instanceof ByteBuf) {
@ -90,6 +84,7 @@ public class DefaultHttp2FrameWriterTest {
when(ctx.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT);
when(ctx.channel()).thenReturn(channel);
when(ctx.executor()).thenReturn(ImmediateEventExecutor.INSTANCE);
when(ctx.newPromise()).thenReturn(ImmediateEventExecutor.INSTANCE.newPromise());
}
@AfterEach
@ -105,7 +100,7 @@ public class DefaultHttp2FrameWriterTest {
Http2Headers headers = new DefaultHttp2Headers()
.method("GET").path("/").authority("foo.com").scheme("https");
frameWriter.writeHeaders(ctx, streamId, headers, 0, true, promise);
frameWriter.writeHeaders(ctx, streamId, headers, 0, true);
byte[] expectedPayload = headerPayload(streamId, headers);
byte[] expectedFrameBytes = {
@ -124,7 +119,7 @@ public class DefaultHttp2FrameWriterTest {
Http2Headers headers = new DefaultHttp2Headers()
.method("GET").path("/").authority("foo.com").scheme("https");
frameWriter.writeHeaders(ctx, streamId, headers, 5, true, promise);
frameWriter.writeHeaders(ctx, streamId, headers, 5, true);
byte[] expectedPayload = headerPayload(streamId, headers, (byte) 4);
byte[] expectedFrameBytes = {
@ -143,7 +138,7 @@ public class DefaultHttp2FrameWriterTest {
Http2Headers headers = new DefaultHttp2Headers()
.method("GET").path("/").authority("foo.com").scheme("https");
frameWriter.writeHeaders(ctx, streamId, headers, 0, false, promise);
frameWriter.writeHeaders(ctx, streamId, headers, 0, false);
byte[] expectedPayload = headerPayload(streamId, headers);
byte[] expectedFrameBytes = {
@ -170,7 +165,7 @@ public class DefaultHttp2FrameWriterTest {
http2HeadersEncoder.configuration().maxHeaderListSize(Integer.MAX_VALUE);
frameWriter.headersConfiguration().maxHeaderListSize(Integer.MAX_VALUE);
frameWriter.maxFrameSize(Http2CodecUtil.MAX_FRAME_SIZE_LOWER_BOUND);
frameWriter.writeHeaders(ctx, streamId, headers, 0, true, promise);
frameWriter.writeHeaders(ctx, streamId, headers, 0, true);
byte[] expectedPayload = headerPayload(streamId, headers);
@ -211,7 +206,7 @@ public class DefaultHttp2FrameWriterTest {
http2HeadersEncoder.configuration().maxHeaderListSize(Integer.MAX_VALUE);
frameWriter.headersConfiguration().maxHeaderListSize(Integer.MAX_VALUE);
frameWriter.maxFrameSize(Http2CodecUtil.MAX_FRAME_SIZE_LOWER_BOUND);
frameWriter.writeHeaders(ctx, streamId, headers, 5, true, promise);
frameWriter.writeHeaders(ctx, streamId, headers, 5, true);
byte[] expectedPayload = buildLargeHeaderPayload(streamId, headers, (byte) 4,
Http2CodecUtil.MAX_FRAME_SIZE_LOWER_BOUND);
@ -245,7 +240,7 @@ public class DefaultHttp2FrameWriterTest {
@Test
public void writeFrameZeroPayload() throws Exception {
frameWriter.writeFrame(ctx, (byte) 0xf, 0, new Http2Flags(), Unpooled.EMPTY_BUFFER, promise);
frameWriter.writeFrame(ctx, (byte) 0xf, 0, new Http2Flags(), Unpooled.EMPTY_BUFFER);
byte[] expectedFrameBytes = {
(byte) 0x00, (byte) 0x00, (byte) 0x00, // payload length
@ -264,7 +259,7 @@ public class DefaultHttp2FrameWriterTest {
// will auto release after frameWriter.writeFrame succeed
ByteBuf payloadByteBuf = Unpooled.wrappedBuffer(payload);
frameWriter.writeFrame(ctx, (byte) 0xf, 0, new Http2Flags(), payloadByteBuf, promise);
frameWriter.writeFrame(ctx, (byte) 0xf, 0, new Http2Flags(), payloadByteBuf);
byte[] expectedFrameHeaderBytes = {
(byte) 0x00, (byte) 0x00, (byte) 0x05, // payload length
@ -279,7 +274,7 @@ public class DefaultHttp2FrameWriterTest {
@Test
public void writePriority() {
frameWriter.writePriority(
ctx, /* streamId= */ 1, /* dependencyId= */ 2, /* weight= */ (short) 256, /* exclusive= */ true, promise);
ctx, /* streamId= */ 1, /* dependencyId= */ 2, /* weight= */ (short) 256, /* exclusive= */ true);
expectedOutbound = Unpooled.copiedBuffer(new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x05, // payload length = 5
@ -295,7 +290,7 @@ public class DefaultHttp2FrameWriterTest {
@Test
public void writePriorityDefaults() {
frameWriter.writePriority(
ctx, /* streamId= */ 1, /* dependencyId= */ 0, /* weight= */ (short) 16, /* exclusive= */ false, promise);
ctx, /* streamId= */ 1, /* dependencyId= */ 0, /* weight= */ (short) 16, /* exclusive= */ false);
expectedOutbound = Unpooled.copiedBuffer(new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x05, // payload length = 5

View File

@ -417,17 +417,16 @@ public class DefaultHttp2LocalFlowControllerTest {
}
private void verifyWindowUpdateSent(int streamId, int windowSizeIncrement) {
verify(frameWriter).writeWindowUpdate(eq(ctx), eq(streamId), eq(windowSizeIncrement), eq(promise));
verify(frameWriter).writeWindowUpdate(eq(ctx), eq(streamId), eq(windowSizeIncrement));
}
private void verifyWindowUpdateNotSent(int streamId) {
verify(frameWriter, never()).writeWindowUpdate(eq(ctx), eq(streamId), anyInt(), eq(promise));
verify(frameWriter, never()).writeWindowUpdate(eq(ctx), eq(streamId), anyInt());
}
@SuppressWarnings("unchecked")
private void verifyWindowUpdateNotSent() {
verify(frameWriter, never()).writeWindowUpdate(any(ChannelHandlerContext.class), anyInt(), anyInt(),
any(Promise.class));
verify(frameWriter, never()).writeWindowUpdate(any(ChannelHandlerContext.class), anyInt(), anyInt());
}
private int window(int streamId) {

View File

@ -55,8 +55,10 @@ import static io.netty.handler.codec.http2.Http2Stream.State.IDLE;
import static io.netty.util.CharsetUtil.US_ASCII;
import static io.netty.util.CharsetUtil.UTF_8;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.any;
@ -157,7 +159,7 @@ public class Http2ConnectionHandlerTest {
buf.release();
return future;
}).when(frameWriter).writeGoAway(
any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class), any(Promise.class));
any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class));
doAnswer((Answer<Future<Void>>) invocation -> {
Object o = invocation.getArguments()[0];
if (o instanceof FutureListener) {
@ -167,6 +169,7 @@ public class Http2ConnectionHandlerTest {
}).when(future).addListener(any(FutureListener.class));
when(future.cause()).thenReturn(fakeException);
when(channel.isActive()).thenReturn(true);
when(future.isFailed()).thenReturn(true);
when(channel.pipeline()).thenReturn(pipeline);
when(connection.remote()).thenReturn(remote);
when(remote.flowController()).thenReturn(remoteFlowController);
@ -185,10 +188,13 @@ public class Http2ConnectionHandlerTest {
when(connection.goAwaySent(anyInt(), anyLong(), any(ByteBuf.class))).thenReturn(true);
when(stream.open(anyBoolean())).thenReturn(stream);
when(encoder.writeSettings(any(ChannelHandlerContext.class),
any(Http2Settings.class), eq(promise))).thenReturn(future);
any(Http2Settings.class))).thenReturn(future);
when(ctx.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT);
when(ctx.channel()).thenReturn(channel);
when(ctx.newSucceededFuture()).thenReturn(future);
when(ctx.newFailedFuture(any(Throwable.class)))
.thenAnswer(invocationOnMock ->
DefaultPromise.newFailedPromise(executor, invocationOnMock.getArgument(0)));
when(ctx.newSucceededFuture()).thenReturn(DefaultPromise.newSuccessfulPromise(executor, null));
when(ctx.newPromise()).thenReturn(promise);
when(ctx.write(any())).thenReturn(future);
when(ctx.executor()).thenReturn(executor);
@ -254,7 +260,7 @@ public class Http2ConnectionHandlerTest {
final Answer<Object> verifier = in -> {
assertEquals(in.getArgument(0), evt); // sanity check...
verify(ctx).write(eq(connectionPrefaceBuf()));
verify(encoder).writeSettings(eq(ctx), any(Http2Settings.class), any(Promise.class));
verify(encoder).writeSettings(eq(ctx), any(Http2Settings.class));
verified.set(true);
return null;
};
@ -292,7 +298,7 @@ public class Http2ConnectionHandlerTest {
handler.channelRead(ctx, copiedBuffer("BAD_PREFACE", UTF_8));
ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);
verify(frameWriter).writeGoAway(any(ChannelHandlerContext.class),
eq(Integer.MAX_VALUE), eq(PROTOCOL_ERROR.code()), captor.capture(), eq(promise));
eq(Integer.MAX_VALUE), eq(PROTOCOL_ERROR.code()), captor.capture());
assertEquals(0, captor.getValue().refCnt());
}
@ -303,7 +309,7 @@ public class Http2ConnectionHandlerTest {
handler.channelRead(ctx, copiedBuffer("GET /path HTTP/1.1", US_ASCII));
ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);
verify(frameWriter).writeGoAway(any(ChannelHandlerContext.class), eq(Integer.MAX_VALUE),
eq(PROTOCOL_ERROR.code()), captor.capture(), eq(promise));
eq(PROTOCOL_ERROR.code()), captor.capture());
assertEquals(0, captor.getValue().refCnt());
assertTrue(goAwayDebugCap.contains("/path"));
}
@ -319,7 +325,7 @@ public class Http2ConnectionHandlerTest {
handler.channelRead(ctx, buf);
ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);
verify(frameWriter, atLeastOnce()).writeGoAway(any(ChannelHandlerContext.class),
eq(Integer.MAX_VALUE), eq(PROTOCOL_ERROR.code()), captor.capture(), eq(promise));
eq(Integer.MAX_VALUE), eq(PROTOCOL_ERROR.code()), captor.capture());
assertEquals(0, captor.getValue().refCnt());
}
@ -373,7 +379,7 @@ public class Http2ConnectionHandlerTest {
handler.exceptionCaught(ctx, e);
ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);
verify(frameWriter).writeGoAway(eq(ctx), eq(Integer.MAX_VALUE), eq(PROTOCOL_ERROR.code()),
captor.capture(), eq(promise));
captor.capture());
captor.getValue().release();
}
@ -389,16 +395,16 @@ public class Http2ConnectionHandlerTest {
when(stream.isHeadersSent()).thenReturn(false);
when(remote.lastStreamCreated()).thenReturn(STREAM_ID);
when(frameWriter.writeRstStream(eq(ctx), eq(STREAM_ID),
eq(PROTOCOL_ERROR.code()), eq(promise))).thenReturn(future);
eq(PROTOCOL_ERROR.code()))).thenReturn(future);
handler.exceptionCaught(ctx, e);
ArgumentCaptor<Http2Headers> captor = ArgumentCaptor.forClass(Http2Headers.class);
verify(encoder).writeHeaders(eq(ctx), eq(STREAM_ID),
captor.capture(), eq(padding), eq(true), eq(promise));
captor.capture(), eq(padding), eq(true));
Http2Headers headers = captor.getValue();
assertEquals(HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE.codeAsText(), headers.status());
verify(frameWriter).writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code(), promise);
verify(frameWriter).writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code());
}
@Test
@ -413,13 +419,13 @@ public class Http2ConnectionHandlerTest {
when(stream.isHeadersSent()).thenReturn(false);
when(remote.lastStreamCreated()).thenReturn(STREAM_ID);
when(frameWriter.writeRstStream(eq(ctx), eq(STREAM_ID),
eq(PROTOCOL_ERROR.code()), eq(promise))).thenReturn(future);
eq(PROTOCOL_ERROR.code()))).thenReturn(future);
handler.exceptionCaught(ctx, e);
verify(encoder, never()).writeHeaders(eq(ctx), eq(STREAM_ID),
any(Http2Headers.class), eq(padding), eq(true), eq(promise));
verify(frameWriter).writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code(), promise);
any(Http2Headers.class), eq(padding), eq(true));
verify(frameWriter).writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code());
}
@Test
@ -434,13 +440,13 @@ public class Http2ConnectionHandlerTest {
when(stream.isHeadersSent()).thenReturn(false);
when(remote.lastStreamCreated()).thenReturn(STREAM_ID);
when(frameWriter.writeRstStream(eq(ctx), eq(STREAM_ID),
eq(PROTOCOL_ERROR.code()), eq(promise))).thenReturn(future);
eq(PROTOCOL_ERROR.code()))).thenReturn(future);
handler.exceptionCaught(ctx, e);
verify(encoder, never()).writeHeaders(eq(ctx), eq(STREAM_ID),
any(Http2Headers.class), eq(padding), eq(true), eq(promise));
verify(frameWriter).writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code(), promise);
any(Http2Headers.class), eq(padding), eq(true));
verify(frameWriter).writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code());
}
@Test
@ -448,7 +454,7 @@ public class Http2ConnectionHandlerTest {
final CountDownLatch latch = new CountDownLatch(1);
handler = new Http2ConnectionHandler(decoder, encoder, new Http2Settings()) {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt == Http2ConnectionPrefaceAndSettingsFrameWrittenEvent.INSTANCE) {
latch.countDown();
}
@ -470,13 +476,13 @@ public class Http2ConnectionHandlerTest {
when(stream.isHeadersSent()).thenReturn(true);
when(remote.lastStreamCreated()).thenReturn(STREAM_ID);
when(frameWriter.writeRstStream(eq(ctx), eq(STREAM_ID),
eq(PROTOCOL_ERROR.code()), eq(promise))).thenReturn(future);
eq(PROTOCOL_ERROR.code()))).thenReturn(future);
handler.exceptionCaught(ctx, e);
verify(encoder, never()).writeHeaders(eq(ctx), eq(STREAM_ID),
any(Http2Headers.class), eq(padding), eq(true), eq(promise));
any(Http2Headers.class), eq(padding), eq(true));
verify(frameWriter).writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code(), promise);
verify(frameWriter).writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code());
}
@Test
@ -494,14 +500,14 @@ public class Http2ConnectionHandlerTest {
when(stream.isHeadersSent()).thenReturn(false);
when(remote.lastStreamCreated()).thenReturn(STREAM_ID);
when(frameWriter.writeRstStream(eq(ctx), eq(STREAM_ID),
eq(PROTOCOL_ERROR.code()), eq(promise))).thenReturn(future);
eq(PROTOCOL_ERROR.code()))).thenReturn(future);
handler.exceptionCaught(ctx, e);
verify(remote).createStream(STREAM_ID, true);
verify(encoder).writeHeaders(eq(ctx), eq(STREAM_ID),
any(Http2Headers.class), eq(padding), eq(true), eq(promise));
any(Http2Headers.class), eq(padding), eq(true));
verify(frameWriter).writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code(), promise);
verify(frameWriter).writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code());
}
@Test
@ -518,9 +524,9 @@ public class Http2ConnectionHandlerTest {
public void writeRstOnNonExistantStreamShouldSucceed() throws Exception {
handler = newHandler();
when(frameWriter.writeRstStream(eq(ctx), eq(NON_EXISTANT_STREAM_ID),
eq(STREAM_CLOSED.code()), eq(promise))).thenReturn(future);
handler.resetStream(ctx, NON_EXISTANT_STREAM_ID, STREAM_CLOSED.code(), promise);
verify(frameWriter).writeRstStream(eq(ctx), eq(NON_EXISTANT_STREAM_ID), eq(STREAM_CLOSED.code()), eq(promise));
eq(STREAM_CLOSED.code()))).thenReturn(future);
handler.resetStream(ctx, NON_EXISTANT_STREAM_ID, STREAM_CLOSED.code());
verify(frameWriter).writeRstStream(eq(ctx), eq(NON_EXISTANT_STREAM_ID), eq(STREAM_CLOSED.code()));
}
@Test
@ -528,21 +534,21 @@ public class Http2ConnectionHandlerTest {
handler = newHandler();
when(stream.id()).thenReturn(STREAM_ID);
when(frameWriter.writeRstStream(eq(ctx), eq(STREAM_ID),
anyLong(), any(Promise.class))).thenReturn(future);
anyLong())).thenReturn(future);
when(stream.state()).thenReturn(CLOSED);
when(stream.isHeadersSent()).thenReturn(true);
// The stream is "closed" but is still known about by the connection (connection().stream(..)
// will return the stream). We should still write a RST_STREAM frame in this scenario.
handler.resetStream(ctx, STREAM_ID, STREAM_CLOSED.code(), promise);
verify(frameWriter).writeRstStream(eq(ctx), eq(STREAM_ID), anyLong(), any(Promise.class));
handler.resetStream(ctx, STREAM_ID, STREAM_CLOSED.code());
verify(frameWriter).writeRstStream(eq(ctx), eq(STREAM_ID), anyLong());
}
@Test
public void writeRstOnIdleStreamShouldNotWriteButStillSucceed() throws Exception {
handler = newHandler();
when(stream.state()).thenReturn(IDLE);
handler.resetStream(ctx, STREAM_ID, STREAM_CLOSED.code(), promise);
verify(frameWriter, never()).writeRstStream(eq(ctx), eq(STREAM_ID), anyLong(), any(Promise.class));
handler.resetStream(ctx, STREAM_ID, STREAM_CLOSED.code());
verify(frameWriter, never()).writeRstStream(eq(ctx), eq(STREAM_ID), anyLong());
verify(stream).close();
}
@ -585,11 +591,10 @@ public class Http2ConnectionHandlerTest {
return null;
}).when(future).addListener(any(FutureListener.class));
handler = newHandler();
handler.goAway(ctx, STREAM_ID, errorCode, data, promise);
handler.goAway(ctx, STREAM_ID, errorCode, data);
verify(connection).goAwaySent(eq(STREAM_ID), eq(errorCode), eq(data));
verify(frameWriter).writeGoAway(eq(ctx), eq(STREAM_ID), eq(errorCode), eq(data),
eq(promise));
verify(frameWriter).writeGoAway(eq(ctx), eq(STREAM_ID), eq(errorCode), eq(data));
verify(ctx).close();
assertEquals(0, data.refCnt());
}
@ -600,13 +605,12 @@ public class Http2ConnectionHandlerTest {
ByteBuf data = dummyData();
long errorCode = Http2Error.INTERNAL_ERROR.code();
handler.goAway(ctx, STREAM_ID + 2, errorCode, data.retain(), promise);
verify(frameWriter).writeGoAway(eq(ctx), eq(STREAM_ID + 2), eq(errorCode), eq(data),
eq(promise));
handler.goAway(ctx, STREAM_ID + 2, errorCode, data.retain());
verify(frameWriter).writeGoAway(eq(ctx), eq(STREAM_ID + 2), eq(errorCode), eq(data));
verify(connection).goAwaySent(eq(STREAM_ID + 2), eq(errorCode), eq(data));
promise = new DefaultPromise<>(ImmediateEventExecutor.INSTANCE);
handler.goAway(ctx, STREAM_ID, errorCode, data, promise);
verify(frameWriter).writeGoAway(eq(ctx), eq(STREAM_ID), eq(errorCode), eq(data), eq(promise));
handler.goAway(ctx, STREAM_ID, errorCode, data);
verify(frameWriter).writeGoAway(eq(ctx), eq(STREAM_ID), eq(errorCode), eq(data));
verify(connection).goAwaySent(eq(STREAM_ID), eq(errorCode), eq(data));
assertEquals(0, data.refCnt());
}
@ -617,20 +621,24 @@ public class Http2ConnectionHandlerTest {
ByteBuf data = dummyData();
long errorCode = Http2Error.INTERNAL_ERROR.code();
handler.goAway(ctx, STREAM_ID, errorCode, data.retain(), promise);
Future<Void> future = handler.goAway(ctx, STREAM_ID, errorCode, data.retain());
verify(connection).goAwaySent(eq(STREAM_ID), eq(errorCode), eq(data));
verify(frameWriter).writeGoAway(eq(ctx), eq(STREAM_ID), eq(errorCode), eq(data), eq(promise));
verify(frameWriter).writeGoAway(eq(ctx), eq(STREAM_ID), eq(errorCode), eq(data));
// The frameWriter is only mocked, so it should not have interacted with the promise.
assertFalse(promise.isDone());
assertFalse(future.isDone());
when(connection.goAwaySent()).thenReturn(true);
when(remote.lastStreamKnownByPeer()).thenReturn(STREAM_ID);
Exception ex = new IllegalStateException();
doAnswer((Answer<Boolean>) invocationOnMock -> {
throw new IllegalStateException();
throw ex;
}).when(connection).goAwaySent(anyInt(), anyLong(), any(ByteBuf.class));
handler.goAway(ctx, STREAM_ID + 2, errorCode, data, promise);
assertTrue(promise.isDone());
assertFalse(promise.isSuccess());
Future<Void> future2 = handler.goAway(ctx, STREAM_ID + 2, errorCode, data);
assertTrue(future2.isDone());
assertFalse(future2.isSuccess());
assertSame(ex, future2.cause());
assertEquals(0, data.refCnt());
verifyNoMoreInteractions(frameWriter);
}
@ -657,9 +665,8 @@ public class Http2ConnectionHandlerTest {
when(channel.isActive()).thenReturn(false);
handler.channelInactive(ctx);
verify(frameWriter, never()).writeGoAway(any(ChannelHandlerContext.class), anyInt(), anyLong(),
any(ByteBuf.class), any(Promise.class));
verify(frameWriter, never()).writeRstStream(any(ChannelHandlerContext.class), anyInt(), anyLong(),
any(Promise.class));
any(ByteBuf.class));
verify(frameWriter, never()).writeRstStream(any(ChannelHandlerContext.class), anyInt(), anyLong());
}
@Test
@ -729,22 +736,14 @@ public class Http2ConnectionHandlerTest {
return stream;
});
when(stream.isResetSent()).then((Answer<Boolean>) invocationOnMock -> resetSent.get());
when(frameWriter.writeRstStream(eq(ctx), eq(STREAM_ID), anyLong(), any(Promise.class)))
.then((Answer<Future<Void>>) invocationOnMock -> {
Promise<Void> promise = invocationOnMock.getArgument(3);
return promise.setSuccess(null);
});
when(frameWriter.writeRstStream(eq(ctx), eq(STREAM_ID), anyLong()))
.thenReturn(ImmediateEventExecutor.INSTANCE.newSucceededFuture(null));
Promise<Void> promise =
new DefaultPromise<>(ImmediateEventExecutor.INSTANCE);
final Promise<Void> promise2 =
new DefaultPromise<>(ImmediateEventExecutor.INSTANCE);
promise.addListener(future -> handler.resetStream(ctx, STREAM_ID, STREAM_CLOSED.code(), promise2));
handler.resetStream(ctx, STREAM_ID, CANCEL.code(), promise);
verify(frameWriter).writeRstStream(eq(ctx), eq(STREAM_ID), anyLong(), any(Promise.class));
assertTrue(promise.isSuccess());
assertTrue(promise2.isSuccess());
Future<Void> f1 = handler.resetStream(ctx, STREAM_ID, STREAM_CLOSED.code());
Future<Void> f2 = handler.resetStream(ctx, STREAM_ID, CANCEL.code());
verify(frameWriter).writeRstStream(eq(ctx), eq(STREAM_ID), anyLong());
assertTrue(f1.isSuccess());
assertTrue(f2.isSuccess());
}
private static ByteBuf dummyData() {

View File

@ -35,6 +35,7 @@ import io.netty.util.AsciiString;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -152,8 +153,7 @@ public class Http2ConnectionRoundtripTest {
(Integer) invocationOnMock.getArgument(1),
(Http2Headers) invocationOnMock.getArgument(2),
0,
false,
ctx.newPromise());
false);
http2Server.flush(ctx);
return null;
}).when(serverListener).onHeadersRead(any(ChannelHandlerContext.class), anyInt(), any(Http2Headers.class),
@ -169,14 +169,14 @@ public class Http2ConnectionRoundtripTest {
final short weight = 16;
final Http2Headers headers = dummyHeaders();
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, weight, false, 0, false, newPromise());
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, weight, false, 0, false);
http2Client.flush(ctx());
http2Client.encoder().writeRstStream(ctx(), 3, Http2Error.INTERNAL_ERROR.code(), newPromise());
http2Client.encoder().writeRstStream(ctx(), 3, Http2Error.INTERNAL_ERROR.code());
http2Client.flush(ctx());
});
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 5, headers, 0, weight, false, 0, false, newPromise());
http2Client.encoder().writeHeaders(ctx(), 5, headers, 0, weight, false, 0, false);
http2Client.flush(ctx());
});
@ -191,8 +191,7 @@ public class Http2ConnectionRoundtripTest {
final short weight = 16;
final Http2Headers headers = dummyHeaders();
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, weight, false, 0, true,
newPromise());
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, weight, false, 0, true);
http2Client.flush(ctx());
});
@ -250,18 +249,17 @@ public class Http2ConnectionRoundtripTest {
runInChannel(serverConnectedChannel, () -> {
http2Server.encoder().writeSettings(serverCtx(),
new Http2Settings().copyFrom(http2Server.decoder().localSettings())
.maxHeaderListSize(100),
serverNewPromise());
.maxHeaderListSize(100));
http2Server.flush(serverCtx());
});
assertTrue(serverSettingsAckLatch1.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS));
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, false, newPromise())
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, false)
.addListener(future -> clientHeadersWriteException.set(future.cause()));
// It is expected that this write should fail locally and the remote peer will never see this.
http2Client.encoder().writeData(ctx(), 3, Unpooled.buffer(), 0, true, newPromise())
http2Client.encoder().writeData(ctx(), 3, Unpooled.buffer(), 0, true)
.addListener(future -> {
clientDataWriteException.set(future.cause());
clientDataWrite.countDown();
@ -277,8 +275,7 @@ public class Http2ConnectionRoundtripTest {
runInChannel(serverConnectedChannel, () -> {
http2Server.encoder().writeSettings(serverCtx(),
new Http2Settings().copyFrom(http2Server.decoder().localSettings())
.maxHeaderListSize(Http2CodecUtil.MAX_HEADER_LIST_SIZE),
serverNewPromise());
.maxHeaderListSize(Http2CodecUtil.MAX_HEADER_LIST_SIZE));
http2Server.flush(serverCtx());
});
@ -286,8 +283,7 @@ public class Http2ConnectionRoundtripTest {
assertTrue(serverSettingsAckLatch2.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS));
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 5, headers, 0, true,
newPromise()).addListener(future -> {
http2Client.encoder().writeHeaders(ctx(), 5, headers, 0, true).addListener(future -> {
clientHeadersWriteException2.set(future.cause());
clientHeadersLatch.countDown();
});
@ -344,8 +340,7 @@ public class Http2ConnectionRoundtripTest {
runInChannel(serverConnectedChannel, () -> {
http2Server.encoder().writeSettings(serverCtx(),
new Http2Settings().copyFrom(http2Server.decoder().localSettings())
.initialWindowSize(0),
serverNewPromise());
.initialWindowSize(0));
http2Server.flush(serverCtx());
});
@ -354,9 +349,8 @@ public class Http2ConnectionRoundtripTest {
// The client should now attempt to send data, but the window size is 0 so it will be queued in the flow
// controller.
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false,
newPromise());
http2Client.encoder().writeData(ctx(), 3, Unpooled.wrappedBuffer(data), 0, true, newPromise());
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false);
http2Client.encoder().writeData(ctx(), 3, Unpooled.wrappedBuffer(data), 0, true);
http2Client.flush(ctx());
clientWriteDataLatch.countDown();
});
@ -367,8 +361,7 @@ public class Http2ConnectionRoundtripTest {
runInChannel(serverConnectedChannel, () -> {
http2Server.encoder().writeSettings(serverCtx(),
new Http2Settings().copyFrom(http2Server.decoder().localSettings())
.initialWindowSize(data.length),
serverNewPromise());
.initialWindowSize(data.length));
http2Server.flush(serverCtx());
});
@ -391,9 +384,8 @@ public class Http2ConnectionRoundtripTest {
final Http2Headers headers = dummyHeaders();
runInChannel(clientChannel, () -> {
http2Client.encoder().writePriority(ctx(), 5, 3, (short) 14, false, newPromise());
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false,
newPromise());
http2Client.encoder().writePriority(ctx(), 5, 3, (short) 14, false);
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false);
http2Client.flush(ctx());
});
@ -420,10 +412,8 @@ public class Http2ConnectionRoundtripTest {
final Http2Headers headers = dummyHeaders();
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 5, headers, 0, (short) 16, false, 0, false,
newPromise());
http2Client.encoder().frameWriter().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false,
newPromise());
http2Client.encoder().writeHeaders(ctx(), 5, headers, 0, (short) 16, false, 0, false);
http2Client.encoder().frameWriter().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false);
http2Client.flush(ctx());
});
@ -465,8 +455,8 @@ public class Http2ConnectionRoundtripTest {
final Http2Headers headers = dummyHeaders();
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), streamId, headers, CONNECTION_STREAM_ID,
DEFAULT_PRIORITY_WEIGHT, false, 0, false, newPromise());
http2Client.encoder().writeRstStream(ctx(), streamId, Http2Error.CANCEL.code(), newPromise());
DEFAULT_PRIORITY_WEIGHT, false, 0, false);
http2Client.encoder().writeRstStream(ctx(), streamId, Http2Error.CANCEL.code());
http2Client.flush(ctx());
});
@ -478,7 +468,7 @@ public class Http2ConnectionRoundtripTest {
// Now have the server attempt to send a headers frame simulating some asynchronous work.
runInChannel(serverConnectedChannel, () -> {
http2Server.encoder().writeHeaders(serverCtx(), streamId, headers, 0, true, serverNewPromise())
http2Server.encoder().writeHeaders(serverCtx(), streamId, headers, 0, true)
.addListener(future -> {
serverWriteHeadersCauseRef.set(future.cause());
serverWriteHeadersLatch.countDown();
@ -510,8 +500,7 @@ public class Http2ConnectionRoundtripTest {
// Create a single stream by sending a HEADERS frame to the server.
final Http2Headers headers = dummyHeaders();
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false,
newPromise());
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false);
http2Client.flush(ctx());
});
@ -547,8 +536,7 @@ public class Http2ConnectionRoundtripTest {
// Create a single stream by sending a HEADERS frame to the server.
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false,
newPromise());
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false);
http2Client.flush(ctx());
});
@ -592,41 +580,42 @@ public class Http2ConnectionRoundtripTest {
throws Exception {
bootstrapEnv(1, 1, 2, 1);
final Promise<Void> emptyDataPromise = newPromise();
Promise<Void> promise = ImmediateEventExecutor.INSTANCE.newPromise();
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 3, EmptyHttp2Headers.INSTANCE, 0, (short) 16, false, 0, false,
newPromise());
http2Client.encoder().writeHeaders(ctx(), 3, EmptyHttp2Headers.INSTANCE, 0, (short) 16, false, 0, false);
ByteBuf emptyBuf = Unpooled.buffer();
emptyBuf.release();
final Future<Void> future;
switch (mode) {
case SINGLE_END_OF_STREAM:
http2Client.encoder().writeData(ctx(), 3, emptyBuf, 0, true, emptyDataPromise);
future = http2Client.encoder().writeData(ctx(), 3, emptyBuf, 0, true);
break;
case SECOND_END_OF_STREAM:
http2Client.encoder().writeData(ctx(), 3, emptyBuf, 0, false, emptyDataPromise);
http2Client.encoder().writeData(ctx(), 3, randomBytes(8), 0, true, newPromise());
future = http2Client.encoder().writeData(ctx(), 3, emptyBuf, 0, false);
http2Client.encoder().writeData(ctx(), 3, randomBytes(8), 0, true);
break;
case SINGLE_WITH_TRAILERS:
http2Client.encoder().writeData(ctx(), 3, emptyBuf, 0, false, emptyDataPromise);
future = http2Client.encoder().writeData(ctx(), 3, emptyBuf, 0, false);
http2Client.encoder().writeHeaders(ctx(), 3, EmptyHttp2Headers.INSTANCE, 0,
(short) 16, false, 0, true, newPromise());
(short) 16, false, 0, true);
break;
case SECOND_WITH_TRAILERS:
http2Client.encoder().writeData(ctx(), 3, emptyBuf, 0, false, emptyDataPromise);
http2Client.encoder().writeData(ctx(), 3, randomBytes(8), 0, false, newPromise());
future = http2Client.encoder().writeData(ctx(), 3, emptyBuf, 0, false);
http2Client.encoder().writeData(ctx(), 3, randomBytes(8), 0, false);
http2Client.encoder().writeHeaders(ctx(), 3, EmptyHttp2Headers.INSTANCE, 0,
(short) 16, false, 0, true, newPromise());
(short) 16, false, 0, true);
break;
default:
throw new Error();
}
http2Client.flush(ctx());
future.cascadeTo(promise);
});
ExecutionException e = assertThrows(ExecutionException.class, new Executable() {
@Override
public void execute() throws Throwable {
emptyDataPromise.get();
promise.get();
}
});
assertThat(e.getCause(), is(instanceOf(IllegalReferenceCountException.class)));
@ -641,8 +630,7 @@ public class Http2ConnectionRoundtripTest {
final Promise<Void> assertPromise = newPromise();
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 3, EmptyHttp2Headers.INSTANCE, 0, (short) 16, false, 0, false,
newPromise());
http2Client.encoder().writeHeaders(ctx(), 3, EmptyHttp2Headers.INSTANCE, 0, (short) 16, false, 0, false);
clientChannel.pipeline().addFirst(new ChannelHandler() {
@Override
public Future<Void> write(ChannelHandlerContext ctx, Object msg) {
@ -660,7 +648,8 @@ public class Http2ConnectionRoundtripTest {
});
http2Client.encoder().flowController().initialWindowSize(4);
http2Client.encoder().writeData(ctx(), 3, randomBytes(8), 0, false, dataPromise);
http2Client.encoder().writeData(ctx(), 3, randomBytes(8), 0, false)
.cascadeTo(dataPromise);
assertTrue(http2Client.encoder().flowController()
.hasFlowControlled(http2Client.connection().stream(3)));
@ -697,8 +686,7 @@ public class Http2ConnectionRoundtripTest {
// Create a single stream by sending a HEADERS frame to the server.
final Http2Headers headers = dummyHeaders();
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false,
newPromise());
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false);
http2Client.flush(ctx());
});
@ -733,7 +721,7 @@ public class Http2ConnectionRoundtripTest {
final Http2Headers headers = dummyHeaders();
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0,
true, newPromise());
true);
http2Client.flush(ctx());
});
@ -741,7 +729,7 @@ public class Http2ConnectionRoundtripTest {
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), MAX_VALUE + 1, headers, 0, (short) 16, false, 0,
true, newPromise());
true);
http2Client.flush(ctx());
});
@ -768,7 +756,7 @@ public class Http2ConnectionRoundtripTest {
final Http2Headers headers = dummyHeaders();
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0,
false, newPromise());
false);
http2Client.flush(ctx());
});
@ -778,7 +766,7 @@ public class Http2ConnectionRoundtripTest {
assertTrue(requestLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS));
runInChannel(serverChannel, () -> {
http2Server.encoder().writeGoAway(serverCtx(), 3, NO_ERROR.code(), EMPTY_BUFFER, serverNewPromise());
http2Server.encoder().writeGoAway(serverCtx(), 3, NO_ERROR.code(), EMPTY_BUFFER);
http2Server.flush(serverCtx());
});
@ -791,7 +779,7 @@ public class Http2ConnectionRoundtripTest {
final CountDownLatch clientWriteAfterGoAwayLatch = new CountDownLatch(1);
runInChannel(clientChannel, () -> {
Future<Void> f = http2Client.encoder().writeHeaders(ctx(), 5, headers, 0, (short) 16, false, 0,
true, newPromise());
true);
clientWriteAfterGoAwayFutureRef.set(f);
http2Client.flush(ctx());
f.addListener(future -> clientWriteAfterGoAwayLatch.countDown());
@ -839,9 +827,9 @@ public class Http2ConnectionRoundtripTest {
final Http2Headers headers = dummyHeaders();
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 1, headers, 0, (short) 16, false, 0,
false, newPromise());
false);
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0,
false, newPromise());
false);
http2Client.flush(ctx());
});
@ -851,7 +839,7 @@ public class Http2ConnectionRoundtripTest {
assertTrue(requestLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS));
runInChannel(serverChannel, () -> {
http2Server.encoder().writeGoAway(serverCtx(), 1, NO_ERROR.code(), EMPTY_BUFFER, serverNewPromise());
http2Server.encoder().writeGoAway(serverCtx(), 1, NO_ERROR.code(), EMPTY_BUFFER);
http2Server.flush(serverCtx());
});
@ -911,12 +899,12 @@ public class Http2ConnectionRoundtripTest {
// Create the stream and send all of the data at once.
runInChannel(clientChannel, () -> {
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0,
false, newPromise());
http2Client.encoder().writeData(ctx(), 3, data.retainedDuplicate(), 0, false, newPromise());
false);
http2Client.encoder().writeData(ctx(), 3, data.retainedDuplicate(), 0, false);
// Write trailers.
http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0,
true, newPromise());
true);
http2Client.flush(ctx());
});
@ -989,14 +977,13 @@ public class Http2ConnectionRoundtripTest {
for (int streamId = 3; streamId < upperLimit; streamId += 2) {
// Send a bunch of data on each stream.
http2Client.encoder().writeHeaders(ctx(), streamId, headers, 0, (short) 16,
false, 0, false, newPromise());
http2Client.encoder().writePing(ctx(), false, pingData,
newPromise());
false, 0, false);
http2Client.encoder().writePing(ctx(), false, pingData);
http2Client.encoder().writeData(ctx(), streamId, data.retainedSlice(), 0,
false, newPromise());
false);
// Write trailers.
http2Client.encoder().writeHeaders(ctx(), streamId, headers, 0, (short) 16,
false, 0, true, newPromise());
false, 0, true);
http2Client.flush(ctx());
}
});

View File

@ -33,7 +33,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.ArrayDeque;
@ -59,7 +58,6 @@ import static org.mockito.Mockito.when;
/**
* Tests for {@link Http2ControlFrameLimitEncoder}.
*/
@SuppressWarnings("unchecked")
public class Http2ControlFrameLimitEncoderTest {
private Http2ControlFrameLimitEncoder encoder;
@ -101,22 +99,22 @@ public class Http2ControlFrameLimitEncoderTest {
when(configuration.frameSizePolicy()).thenReturn(frameSizePolicy);
when(frameSizePolicy.maxFrameSize()).thenReturn(DEFAULT_MAX_FRAME_SIZE);
when(writer.writeRstStream(eq(ctx), anyInt(), anyLong(), any(Promise.class)))
.thenAnswer((Answer<Future<Void>>) invocationOnMock -> handlePromise(invocationOnMock, 3));
when(writer.writeSettingsAck(any(ChannelHandlerContext.class), any(Promise.class)))
.thenAnswer((Answer<Future<Void>>) invocationOnMock -> handlePromise(invocationOnMock, 1));
when(writer.writePing(any(ChannelHandlerContext.class), anyBoolean(), anyLong(), any(Promise.class)))
when(writer.writeRstStream(eq(ctx), anyInt(), anyLong()))
.thenAnswer((Answer<Future<Void>>) invocationOnMock -> handlePromise());
when(writer.writeSettingsAck(any(ChannelHandlerContext.class)))
.thenAnswer((Answer<Future<Void>>) invocationOnMock -> handlePromise());
when(writer.writePing(any(ChannelHandlerContext.class), anyBoolean(), anyLong()))
.thenAnswer((Answer<Future<Void>>) invocationOnMock -> {
Promise<Void> promise = handlePromise(invocationOnMock, 3);
Promise<Void> promise = handlePromise();
if (invocationOnMock.getArgument(1) == Boolean.FALSE) {
promise.trySuccess(null);
}
return promise;
});
when(writer.writeGoAway(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class),
any(Promise.class))).thenAnswer((Answer<Future<Void>>) invocationOnMock -> {
when(writer.writeGoAway(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)))
.thenAnswer((Answer<Future<Void>>) invocationOnMock -> {
ReferenceCountUtil.release(invocationOnMock.getArgument(3));
Promise<Void> promise = invocationOnMock.getArgument(4);
Promise<Void> promise = ImmediateEventExecutor.INSTANCE.newPromise();
goAwayPromises.offer(promise);
return promise;
});
@ -139,7 +137,12 @@ public class Http2ControlFrameLimitEncoderTest {
when(channel.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT);
when(executor.inEventLoop()).thenReturn(true);
doAnswer((Answer<Promise>) invocation -> newPromise()).when(ctx).newPromise();
doAnswer((Answer<Future>) invocation ->
ImmediateEventExecutor.INSTANCE.newFailedFuture(invocation.getArgument(0)))
.when(ctx).newFailedFuture(any(Throwable.class));
when(ctx.executor()).thenReturn(executor);
when(ctx.close()).thenReturn(ImmediateEventExecutor.INSTANCE.newSucceededFuture(null));
when(channel.isActive()).thenReturn(false);
when(channel.config()).thenReturn(config);
when(channel.isWritable()).thenReturn(true);
@ -152,12 +155,12 @@ public class Http2ControlFrameLimitEncoderTest {
handler.handlerAdded(ctx);
}
private Promise<Void> handlePromise(InvocationOnMock invocationOnMock, int promiseIdx) {
Promise<Void> promise = invocationOnMock.getArgument(promiseIdx);
private Promise<Void> handlePromise() {
Promise<Void> p = ImmediateEventExecutor.INSTANCE.newPromise();
if (++numWrites == 2) {
promise.setSuccess(null);
p.setSuccess(null);
}
return promise;
return p;
}
@AfterEach
@ -178,75 +181,75 @@ public class Http2ControlFrameLimitEncoderTest {
@Test
public void testLimitSettingsAck() {
assertFalse(encoder.writeSettingsAck(ctx, newPromise()).isDone());
assertFalse(encoder.writeSettingsAck(ctx).isDone());
// The second write is always marked as success by our mock, which means it will also not be queued and so
// not count to the number of queued frames.
assertTrue(encoder.writeSettingsAck(ctx, newPromise()).isSuccess());
assertFalse(encoder.writeSettingsAck(ctx, newPromise()).isDone());
assertTrue(encoder.writeSettingsAck(ctx).isSuccess());
assertFalse(encoder.writeSettingsAck(ctx).isDone());
verifyFlushAndClose(0, false);
assertFalse(encoder.writeSettingsAck(ctx, newPromise()).isDone());
assertFalse(encoder.writeSettingsAck(ctx, newPromise()).isDone());
assertFalse(encoder.writeSettingsAck(ctx).isDone());
assertFalse(encoder.writeSettingsAck(ctx).isDone());
verifyFlushAndClose(1, true);
}
@Test
public void testLimitPingAck() {
assertFalse(encoder.writePing(ctx, true, 8, newPromise()).isDone());
assertFalse(encoder.writePing(ctx, true, 8).isDone());
// The second write is always marked as success by our mock, which means it will also not be queued and so
// not count to the number of queued frames.
assertTrue(encoder.writePing(ctx, true, 8, newPromise()).isSuccess());
assertFalse(encoder.writePing(ctx, true, 8, newPromise()).isDone());
assertTrue(encoder.writePing(ctx, true, 8).isSuccess());
assertFalse(encoder.writePing(ctx, true, 8).isDone());
verifyFlushAndClose(0, false);
assertFalse(encoder.writePing(ctx, true, 8, newPromise()).isDone());
assertFalse(encoder.writePing(ctx, true, 8, newPromise()).isDone());
assertFalse(encoder.writePing(ctx, true, 8).isDone());
assertFalse(encoder.writePing(ctx, true, 8).isDone());
verifyFlushAndClose(1, true);
}
@Test
public void testNotLimitPing() {
assertTrue(encoder.writePing(ctx, false, 8, newPromise()).isSuccess());
assertTrue(encoder.writePing(ctx, false, 8, newPromise()).isSuccess());
assertTrue(encoder.writePing(ctx, false, 8, newPromise()).isSuccess());
assertTrue(encoder.writePing(ctx, false, 8, newPromise()).isSuccess());
assertTrue(encoder.writePing(ctx, false, 8).isSuccess());
assertTrue(encoder.writePing(ctx, false, 8).isSuccess());
assertTrue(encoder.writePing(ctx, false, 8).isSuccess());
assertTrue(encoder.writePing(ctx, false, 8).isSuccess());
verifyFlushAndClose(0, false);
}
@Test
public void testLimitRst() {
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code(), newPromise()).isDone());
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code()).isDone());
// The second write is always marked as success by our mock, which means it will also not be queued and so
// not count to the number of queued frames.
assertTrue(encoder.writeRstStream(ctx, 1, CANCEL.code(), newPromise()).isSuccess());
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code(), newPromise()).isDone());
assertTrue(encoder.writeRstStream(ctx, 1, CANCEL.code()).isSuccess());
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code()).isDone());
verifyFlushAndClose(0, false);
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code(), newPromise()).isDone());
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code(), newPromise()).isDone());
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code()).isDone());
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code()).isDone());
verifyFlushAndClose(1, true);
}
@Test
public void testLimit() {
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code(), newPromise()).isDone());
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code()).isDone());
// The second write is always marked as success by our mock, which means it will also not be queued and so
// not count to the number of queued frames.
assertTrue(encoder.writePing(ctx, false, 8, newPromise()).isSuccess());
assertFalse(encoder.writePing(ctx, true, 8, newPromise()).isSuccess());
assertTrue(encoder.writePing(ctx, false, 8).isSuccess());
assertFalse(encoder.writePing(ctx, true, 8).isSuccess());
verifyFlushAndClose(0, false);
assertFalse(encoder.writeSettingsAck(ctx, newPromise()).isDone());
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code(), newPromise()).isDone());
assertFalse(encoder.writePing(ctx, true, 8, newPromise()).isSuccess());
assertFalse(encoder.writeSettingsAck(ctx).isDone());
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code()).isDone());
assertFalse(encoder.writePing(ctx, true, 8).isSuccess());
verifyFlushAndClose(1, true);
}
@ -256,7 +259,7 @@ public class Http2ControlFrameLimitEncoderTest {
verify(ctx, times(invocations)).close();
if (failed) {
verify(writer, times(1)).writeGoAway(eq(ctx), eq(Integer.MAX_VALUE), eq(ENHANCE_YOUR_CALM.code()),
any(ByteBuf.class), any(Promise.class));
any(ByteBuf.class));
}
}

View File

@ -58,7 +58,6 @@ import java.util.concurrent.atomic.AtomicReference;
import static io.netty.handler.codec.http2.Http2CodecUtil.isStreamIdValid;
import static io.netty.handler.codec.http2.Http2Error.NO_ERROR;
import static io.netty.handler.codec.http2.Http2TestUtil.anyChannelPromise;
import static io.netty.handler.codec.http2.Http2TestUtil.anyHttp2Settings;
import static io.netty.handler.codec.http2.Http2TestUtil.assertEqualsAndRelease;
import static io.netty.handler.codec.http2.Http2TestUtil.bb;
@ -82,7 +81,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Unit tests for {@link Http2FrameCodec}.
*/
@SuppressWarnings("unchecked")
public class Http2FrameCodecTest {
// For verifying outbound frames
@ -141,13 +139,13 @@ public class Http2FrameCodecTest {
channel.pipeline().fireChannelActive();
// Handshake
verify(frameWriter).writeSettings(any(ChannelHandlerContext.class), anyHttp2Settings(), anyChannelPromise());
verify(frameWriter).writeSettings(any(ChannelHandlerContext.class), anyHttp2Settings());
verifyNoMoreInteractions(frameWriter);
channel.writeInbound(Http2CodecUtil.connectionPrefaceBuf());
frameInboundWriter.writeInboundSettings(initialRemoteSettings);
verify(frameWriter).writeSettingsAck(any(ChannelHandlerContext.class), anyChannelPromise());
verify(frameWriter).writeSettingsAck(any(ChannelHandlerContext.class));
frameInboundWriter.writeInboundSettingsAck();
@ -178,9 +176,9 @@ public class Http2FrameCodecTest {
channel.writeOutbound(new DefaultHttp2HeadersFrame(response, true, 27).stream(stream2));
verify(frameWriter).writeHeaders(
any(ChannelHandlerContext.class), eq(1), eq(response),
eq(27), eq(true), anyChannelPromise());
eq(27), eq(true));
verify(frameWriter, never()).writeRstStream(
any(ChannelHandlerContext.class), anyInt(), anyLong(), anyChannelPromise());
any(ChannelHandlerContext.class), anyInt(), anyLong());
assertEquals(State.CLOSED, stream.state());
event = inboundHandler.readInboundMessageOrUserEvent();
@ -207,9 +205,9 @@ public class Http2FrameCodecTest {
channel.writeOutbound(new DefaultHttp2HeadersFrame(response, true, 27).stream(stream2));
verify(frameWriter).writeHeaders(
any(ChannelHandlerContext.class), eq(1), eq(response),
eq(27), eq(true), anyChannelPromise());
eq(27), eq(true));
verify(frameWriter, never()).writeRstStream(
any(ChannelHandlerContext.class), anyInt(), anyLong(), anyChannelPromise());
any(ChannelHandlerContext.class), anyInt(), anyLong());
assertEquals(State.CLOSED, stream.state());
assertTrue(channel.isActive());
@ -266,12 +264,12 @@ public class Http2FrameCodecTest {
inboundHandler.writeOutbound(new DefaultHttp2HeadersFrame(response, false).stream(stream2));
verify(frameWriter).writeHeaders(any(ChannelHandlerContext.class), eq(1), eq(response), eq(0),
eq(false), anyChannelPromise());
eq(false));
channel.writeOutbound(new DefaultHttp2DataFrame(bb("world"), true, 27).stream(stream2));
ArgumentCaptor<ByteBuf> outboundData = ArgumentCaptor.forClass(ByteBuf.class);
verify(frameWriter).writeData(any(ChannelHandlerContext.class), eq(1), outboundData.capture(), eq(27),
eq(true), anyChannelPromise());
eq(true));
ByteBuf bb = bb("world");
assertEquals(bb, outboundData.getValue());
@ -280,7 +278,7 @@ public class Http2FrameCodecTest {
outboundData.getValue().release();
verify(frameWriter, never()).writeRstStream(any(ChannelHandlerContext.class),
anyInt(), anyLong(), anyChannelPromise());
anyInt(), anyLong());
assertTrue(channel.isActive());
}
@ -301,7 +299,7 @@ public class Http2FrameCodecTest {
assertEquals(3, stream2.id());
channel.writeOutbound(new DefaultHttp2ResetFrame(314 /* non-standard error */).stream(stream2));
verify(frameWriter).writeRstStream(any(ChannelHandlerContext.class), eq(3), eq(314L), anyChannelPromise());
verify(frameWriter).writeRstStream(any(ChannelHandlerContext.class), eq(3), eq(314L));
assertEquals(State.CLOSED, stream.state());
assertTrue(channel.isActive());
}
@ -343,7 +341,7 @@ public class Http2FrameCodecTest {
channel.writeOutbound(goAwayFrame);
verify(frameWriter).writeGoAway(any(ChannelHandlerContext.class), eq(7),
eq(NO_ERROR.code()), eq(expected), anyChannelPromise());
eq(NO_ERROR.code()), eq(expected));
assertEquals(State.OPEN, stream.state());
assertTrue(channel.isActive());
expected.release();
@ -419,7 +417,7 @@ public class Http2FrameCodecTest {
channel.writeOutbound(goAwayFrame);
// When the last stream id computation overflows, the last stream id should just be set to 2^31 - 1.
verify(frameWriter).writeGoAway(any(ChannelHandlerContext.class), eq(Integer.MAX_VALUE),
eq(NO_ERROR.code()), eq(debugData), anyChannelPromise());
eq(NO_ERROR.code()), eq(debugData));
debugData.release();
assertEquals(State.OPEN, stream.state());
assertTrue(channel.isActive());
@ -596,7 +594,7 @@ public class Http2FrameCodecTest {
channel.write(unknownFrame);
verify(frameWriter).writeFrame(any(ChannelHandlerContext.class), eq(unknownFrame.frameType()),
eq(unknownFrame.stream().id()), eq(unknownFrame.flags()), eq(buffer), any(Promise.class));
eq(unknownFrame.stream().id()), eq(unknownFrame.flags()), eq(buffer));
}
@Test
@ -604,7 +602,7 @@ public class Http2FrameCodecTest {
Http2Settings settings = new Http2Settings();
channel.write(new DefaultHttp2SettingsFrame(settings));
verify(frameWriter).writeSettings(any(ChannelHandlerContext.class), same(settings), any(Promise.class));
verify(frameWriter).writeSettings(any(ChannelHandlerContext.class), same(settings));
}
@Test
@ -763,7 +761,7 @@ public class Http2FrameCodecTest {
channel.writeAndFlush(new DefaultHttp2PingFrame(12345));
verify(frameWriter).writePing(any(ChannelHandlerContext.class), eq(false),
eq(12345L), anyChannelPromise());
eq(12345L));
}
@Test
@ -781,7 +779,7 @@ public class Http2FrameCodecTest {
Http2Settings settings = new Http2Settings().maxConcurrentStreams(1);
channel.writeAndFlush(new DefaultHttp2SettingsFrame(settings));
verify(frameWriter).writeSettings(any(ChannelHandlerContext.class), eq(settings), anyChannelPromise());
verify(frameWriter).writeSettings(any(ChannelHandlerContext.class), eq(settings));
}
@Test
@ -837,21 +835,21 @@ public class Http2FrameCodecTest {
Http2PingFrame frame = inboundHandler.readInbound();
assertFalse(frame.ack());
assertEquals(8, frame.content());
verify(frameWriter).writePing(any(ChannelHandlerContext.class), eq(true), eq(8L), anyChannelPromise());
verify(frameWriter).writePing(any(ChannelHandlerContext.class), eq(true), eq(8L));
}
@Test
public void autoAckPingFalse() throws Exception {
setUp(Http2FrameCodecBuilder.forServer().autoAckPingFrame(false), new Http2Settings());
frameInboundWriter.writeInboundPing(false, 8);
verify(frameWriter, never()).writePing(any(ChannelHandlerContext.class), eq(true), eq(8L), anyChannelPromise());
verify(frameWriter, never()).writePing(any(ChannelHandlerContext.class), eq(true), eq(8L));
Http2PingFrame frame = inboundHandler.readInbound();
assertFalse(frame.ack());
assertEquals(8, frame.content());
// Now ack the frame manually.
channel.writeAndFlush(new DefaultHttp2PingFrame(8, true));
verify(frameWriter).writePing(any(ChannelHandlerContext.class), eq(true), eq(8L), anyChannelPromise());
verify(frameWriter).writePing(any(ChannelHandlerContext.class), eq(true), eq(8L));
}
@Test

View File

@ -49,59 +49,59 @@ final class Http2FrameInboundWriter {
}
void writeInboundData(int streamId, ByteBuf data, int padding, boolean endStream) {
writer.writeData(ctx, streamId, data, padding, endStream, ctx.newPromise()).syncUninterruptibly();
writer.writeData(ctx, streamId, data, padding, endStream).syncUninterruptibly();
}
void writeInboundHeaders(int streamId, Http2Headers headers,
int padding, boolean endStream) {
writer.writeHeaders(ctx, streamId, headers, padding, endStream, ctx.newPromise()).syncUninterruptibly();
writer.writeHeaders(ctx, streamId, headers, padding, endStream).syncUninterruptibly();
}
void writeInboundHeaders(int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) {
writer.writeHeaders(ctx, streamId, headers, streamDependency,
weight, exclusive, padding, endStream, ctx.newPromise()).syncUninterruptibly();
weight, exclusive, padding, endStream).syncUninterruptibly();
}
void writeInboundPriority(int streamId, int streamDependency,
short weight, boolean exclusive) {
writer.writePriority(ctx, streamId, streamDependency, weight,
exclusive, ctx.newPromise()).syncUninterruptibly();
exclusive).syncUninterruptibly();
}
void writeInboundRstStream(int streamId, long errorCode) {
writer.writeRstStream(ctx, streamId, errorCode, ctx.newPromise()).syncUninterruptibly();
writer.writeRstStream(ctx, streamId, errorCode).syncUninterruptibly();
}
void writeInboundSettings(Http2Settings settings) {
writer.writeSettings(ctx, settings, ctx.newPromise()).syncUninterruptibly();
writer.writeSettings(ctx, settings).syncUninterruptibly();
}
void writeInboundSettingsAck() {
writer.writeSettingsAck(ctx, ctx.newPromise()).syncUninterruptibly();
writer.writeSettingsAck(ctx).syncUninterruptibly();
}
void writeInboundPing(boolean ack, long data) {
writer.writePing(ctx, ack, data, ctx.newPromise()).syncUninterruptibly();
writer.writePing(ctx, ack, data).syncUninterruptibly();
}
void writePushPromise(int streamId, int promisedStreamId,
Http2Headers headers, int padding) {
writer.writePushPromise(ctx, streamId, promisedStreamId,
headers, padding, ctx.newPromise()).syncUninterruptibly();
headers, padding).syncUninterruptibly();
}
void writeInboundGoAway(int lastStreamId, long errorCode, ByteBuf debugData) {
writer.writeGoAway(ctx, lastStreamId, errorCode, debugData, ctx.newPromise()).syncUninterruptibly();
writer.writeGoAway(ctx, lastStreamId, errorCode, debugData).syncUninterruptibly();
}
void writeInboundWindowUpdate(int streamId, int windowSizeIncrement) {
writer.writeWindowUpdate(ctx, streamId, windowSizeIncrement, ctx.newPromise()).syncUninterruptibly();
writer.writeWindowUpdate(ctx, streamId, windowSizeIncrement).syncUninterruptibly();
}
void writeInboundFrame(byte frameType, int streamId,
Http2Flags flags, ByteBuf payload) {
writer.writeFrame(ctx, frameType, streamId, flags, payload, ctx.newPromise()).syncUninterruptibly();
writer.writeFrame(ctx, frameType, streamId, flags, payload).syncUninterruptibly();
}
private static final class WriteInboundChannelHandlerContext

View File

@ -59,7 +59,6 @@ import static org.mockito.Mockito.anyShort;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isA;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -134,7 +133,7 @@ public class Http2FrameRoundtripTest {
@Test
public void emptyDataShouldMatch() throws Exception {
final ByteBuf data = EMPTY_BUFFER;
writer.writeData(ctx, STREAM_ID, data.slice(), 0, false, ctx.newPromise());
writer.writeData(ctx, STREAM_ID, data.slice(), 0, false);
readFrames();
verify(listener).onDataRead(eq(ctx), eq(STREAM_ID), eq(data), eq(0), eq(false));
}
@ -142,7 +141,7 @@ public class Http2FrameRoundtripTest {
@Test
public void dataShouldMatch() throws Exception {
final ByteBuf data = data(10);
writer.writeData(ctx, STREAM_ID, data.slice(), 1, false, ctx.newPromise());
writer.writeData(ctx, STREAM_ID, data.slice(), 1, false);
readFrames();
verify(listener).onDataRead(eq(ctx), eq(STREAM_ID), eq(data), eq(1), eq(false));
}
@ -150,7 +149,7 @@ public class Http2FrameRoundtripTest {
@Test
public void dataWithPaddingShouldMatch() throws Exception {
final ByteBuf data = data(10);
writer.writeData(ctx, STREAM_ID, data.slice(), MAX_PADDING, true, ctx.newPromise());
writer.writeData(ctx, STREAM_ID, data.slice(), MAX_PADDING, true);
readFrames();
verify(listener).onDataRead(eq(ctx), eq(STREAM_ID), eq(data), eq(MAX_PADDING), eq(true));
}
@ -163,7 +162,7 @@ public class Http2FrameRoundtripTest {
final boolean endOfStream = true;
writer.writeData(ctx, STREAM_ID, originalData.slice(), originalPadding,
endOfStream, ctx.newPromise());
endOfStream);
readFrames();
// Verify that at least one frame was sent with eos=false and exactly one with eos=true.
@ -196,7 +195,7 @@ public class Http2FrameRoundtripTest {
@Test
public void emptyHeadersShouldMatch() throws Exception {
final Http2Headers headers = EmptyHttp2Headers.INSTANCE;
writer.writeHeaders(ctx, STREAM_ID, headers, 0, true, ctx.newPromise());
writer.writeHeaders(ctx, STREAM_ID, headers, 0, true);
readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(0), eq(true));
}
@ -204,7 +203,7 @@ public class Http2FrameRoundtripTest {
@Test
public void emptyHeadersWithPaddingShouldMatch() throws Exception {
final Http2Headers headers = EmptyHttp2Headers.INSTANCE;
writer.writeHeaders(ctx, STREAM_ID, headers, MAX_PADDING, true, ctx.newPromise());
writer.writeHeaders(ctx, STREAM_ID, headers, MAX_PADDING, true);
readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(MAX_PADDING), eq(true));
}
@ -212,7 +211,7 @@ public class Http2FrameRoundtripTest {
@Test
public void binaryHeadersWithoutPriorityShouldMatch() throws Exception {
final Http2Headers headers = binaryHeaders();
writer.writeHeaders(ctx, STREAM_ID, headers, 0, true, ctx.newPromise());
writer.writeHeaders(ctx, STREAM_ID, headers, 0, true);
readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(0), eq(true));
}
@ -220,7 +219,7 @@ public class Http2FrameRoundtripTest {
@Test
public void headersFrameWithoutPriorityShouldMatch() throws Exception {
final Http2Headers headers = headers();
writer.writeHeaders(ctx, STREAM_ID, headers, 0, true, ctx.newPromise());
writer.writeHeaders(ctx, STREAM_ID, headers, 0, true);
readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(0), eq(true));
}
@ -228,7 +227,7 @@ public class Http2FrameRoundtripTest {
@Test
public void headersFrameWithPriorityShouldMatch() throws Exception {
final Http2Headers headers = headers();
writer.writeHeaders(ctx, STREAM_ID, headers, 4, (short) 255, true, 0, true, ctx.newPromise());
writer.writeHeaders(ctx, STREAM_ID, headers, 4, (short) 255, true, 0, true);
readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(4), eq((short) 255),
eq(true), eq(0), eq(true));
@ -237,7 +236,7 @@ public class Http2FrameRoundtripTest {
@Test
public void headersWithPaddingWithoutPriorityShouldMatch() throws Exception {
final Http2Headers headers = headers();
writer.writeHeaders(ctx, STREAM_ID, headers, MAX_PADDING, true, ctx.newPromise());
writer.writeHeaders(ctx, STREAM_ID, headers, MAX_PADDING, true);
readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(MAX_PADDING), eq(true));
}
@ -245,7 +244,7 @@ public class Http2FrameRoundtripTest {
@Test
public void headersWithPaddingWithPriorityShouldMatch() throws Exception {
final Http2Headers headers = headers();
writer.writeHeaders(ctx, STREAM_ID, headers, 2, (short) 3, true, 1, true, ctx.newPromise());
writer.writeHeaders(ctx, STREAM_ID, headers, 2, (short) 3, true, 1, true);
readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(2), eq((short) 3), eq(true),
eq(1), eq(true));
@ -254,7 +253,7 @@ public class Http2FrameRoundtripTest {
@Test
public void continuedHeadersShouldMatch() throws Exception {
final Http2Headers headers = largeHeaders();
writer.writeHeaders(ctx, STREAM_ID, headers, 2, (short) 3, true, 0, true, ctx.newPromise());
writer.writeHeaders(ctx, STREAM_ID, headers, 2, (short) 3, true, 0, true);
readFrames();
verify(listener)
.onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(2), eq((short) 3), eq(true), eq(0), eq(true));
@ -263,7 +262,7 @@ public class Http2FrameRoundtripTest {
@Test
public void continuedHeadersWithPaddingShouldMatch() throws Exception {
final Http2Headers headers = largeHeaders();
writer.writeHeaders(ctx, STREAM_ID, headers, 2, (short) 3, true, MAX_PADDING, true, ctx.newPromise());
writer.writeHeaders(ctx, STREAM_ID, headers, 2, (short) 3, true, MAX_PADDING, true);
readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(2), eq((short) 3), eq(true),
eq(MAX_PADDING), eq(true));
@ -275,7 +274,7 @@ public class Http2FrameRoundtripTest {
final int maxListSize = 100;
reader.configuration().headersConfiguration().maxHeaderListSize(maxListSize, maxListSize);
final Http2Headers headers = headersOfSize(maxListSize + 1);
writer.writeHeaders(ctx, STREAM_ID, headers, 2, (short) 3, true, MAX_PADDING, true, ctx.newPromise());
writer.writeHeaders(ctx, STREAM_ID, headers, 2, (short) 3, true, MAX_PADDING, true);
assertThrows(Http2Exception.class, new Executable() {
@Override
public void execute() throws Throwable {
@ -290,7 +289,7 @@ public class Http2FrameRoundtripTest {
@Test
public void emptyPushPromiseShouldMatch() throws Exception {
final Http2Headers headers = EmptyHttp2Headers.INSTANCE;
writer.writePushPromise(ctx, STREAM_ID, 2, headers, 0, ctx.newPromise());
writer.writePushPromise(ctx, STREAM_ID, 2, headers, 0);
readFrames();
verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(2), eq(headers), eq(0));
}
@ -298,7 +297,7 @@ public class Http2FrameRoundtripTest {
@Test
public void pushPromiseFrameShouldMatch() throws Exception {
final Http2Headers headers = headers();
writer.writePushPromise(ctx, STREAM_ID, 1, headers, 5, ctx.newPromise());
writer.writePushPromise(ctx, STREAM_ID, 1, headers, 5);
readFrames();
verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(1), eq(headers), eq(5));
}
@ -306,7 +305,7 @@ public class Http2FrameRoundtripTest {
@Test
public void pushPromiseWithPaddingShouldMatch() throws Exception {
final Http2Headers headers = headers();
writer.writePushPromise(ctx, STREAM_ID, 2, headers, MAX_PADDING, ctx.newPromise());
writer.writePushPromise(ctx, STREAM_ID, 2, headers, MAX_PADDING);
readFrames();
verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(2), eq(headers), eq(MAX_PADDING));
}
@ -314,7 +313,7 @@ public class Http2FrameRoundtripTest {
@Test
public void continuedPushPromiseShouldMatch() throws Exception {
final Http2Headers headers = largeHeaders();
writer.writePushPromise(ctx, STREAM_ID, 2, headers, 0, ctx.newPromise());
writer.writePushPromise(ctx, STREAM_ID, 2, headers, 0);
readFrames();
verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(2), eq(headers), eq(0));
}
@ -322,7 +321,7 @@ public class Http2FrameRoundtripTest {
@Test
public void continuedPushPromiseWithPaddingShouldMatch() throws Exception {
final Http2Headers headers = largeHeaders();
writer.writePushPromise(ctx, STREAM_ID, 2, headers, 0xFF, ctx.newPromise());
writer.writePushPromise(ctx, STREAM_ID, 2, headers, 0xFF);
readFrames();
verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(2), eq(headers), eq(0xFF));
}
@ -332,7 +331,7 @@ public class Http2FrameRoundtripTest {
final String text = "test";
final ByteBuf data = buf(text.getBytes());
writer.writeGoAway(ctx, STREAM_ID, ERROR_CODE, data.slice(), ctx.newPromise());
writer.writeGoAway(ctx, STREAM_ID, ERROR_CODE, data.slice());
readFrames();
ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);
@ -342,7 +341,7 @@ public class Http2FrameRoundtripTest {
@Test
public void pingFrameShouldMatch() throws Exception {
writer.writePing(ctx, false, 1234567, ctx.newPromise());
writer.writePing(ctx, false, 1234567);
readFrames();
ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(long.class);
@ -352,7 +351,7 @@ public class Http2FrameRoundtripTest {
@Test
public void pingAckFrameShouldMatch() throws Exception {
writer.writePing(ctx, true, 1234567, ctx.newPromise());
writer.writePing(ctx, true, 1234567);
readFrames();
ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(long.class);
@ -362,14 +361,14 @@ public class Http2FrameRoundtripTest {
@Test
public void priorityFrameShouldMatch() throws Exception {
writer.writePriority(ctx, STREAM_ID, 1, (short) 1, true, ctx.newPromise());
writer.writePriority(ctx, STREAM_ID, 1, (short) 1, true);
readFrames();
verify(listener).onPriorityRead(eq(ctx), eq(STREAM_ID), eq(1), eq((short) 1), eq(true));
}
@Test
public void rstStreamFrameShouldMatch() throws Exception {
writer.writeRstStream(ctx, STREAM_ID, ERROR_CODE, ctx.newPromise());
writer.writeRstStream(ctx, STREAM_ID, ERROR_CODE);
readFrames();
verify(listener).onRstStreamRead(eq(ctx), eq(STREAM_ID), eq(ERROR_CODE));
}
@ -377,7 +376,7 @@ public class Http2FrameRoundtripTest {
@Test
public void emptySettingsFrameShouldMatch() throws Exception {
final Http2Settings settings = new Http2Settings();
writer.writeSettings(ctx, settings, ctx.newPromise());
writer.writeSettings(ctx, settings);
readFrames();
verify(listener).onSettingsRead(eq(ctx), eq(settings));
}
@ -390,21 +389,21 @@ public class Http2FrameRoundtripTest {
settings.initialWindowSize(123);
settings.maxConcurrentStreams(456);
writer.writeSettings(ctx, settings, ctx.newPromise());
writer.writeSettings(ctx, settings);
readFrames();
verify(listener).onSettingsRead(eq(ctx), eq(settings));
}
@Test
public void settingsAckShouldMatch() throws Exception {
writer.writeSettingsAck(ctx, ctx.newPromise());
writer.writeSettingsAck(ctx);
readFrames();
verify(listener).onSettingsAckRead(eq(ctx));
}
@Test
public void windowUpdateFrameShouldMatch() throws Exception {
writer.writeWindowUpdate(ctx, STREAM_ID, WINDOW_UPDATE, ctx.newPromise());
writer.writeWindowUpdate(ctx, STREAM_ID, WINDOW_UPDATE);
readFrames();
verify(listener).onWindowUpdateRead(eq(ctx), eq(STREAM_ID), eq(WINDOW_UPDATE));
}

View File

@ -28,6 +28,7 @@ import io.netty.handler.codec.http2.Http2Exception.StreamException;
import io.netty.util.AsciiString;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.AfterEach;
@ -49,7 +50,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import static io.netty.handler.codec.http2.Http2TestUtil.anyChannelPromise;
import static io.netty.handler.codec.http2.Http2TestUtil.anyHttp2Settings;
import static io.netty.handler.codec.http2.Http2TestUtil.assertEqualsAndRelease;
import static io.netty.handler.codec.http2.Http2TestUtil.bb;
@ -110,7 +110,7 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
Http2Settings settings = new Http2Settings().initialWindowSize(initialRemoteStreamWindow);
frameInboundWriter.writeInboundSettings(settings);
verify(frameWriter).writeSettingsAck(any(ChannelHandlerContext.class), anyChannelPromise());
verify(frameWriter).writeSettingsAck(any(ChannelHandlerContext.class));
frameInboundWriter.writeInboundSettingsAck();
@ -121,7 +121,7 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
// Handshake
verify(frameWriter).writeSettings(any(ChannelHandlerContext.class),
anyHttp2Settings(), anyChannelPromise());
anyHttp2Settings());
}
@AfterEach
@ -151,7 +151,7 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
assertTrue(childChannel.isActive());
verify(frameWriter).writeFrame(any(ChannelHandlerContext.class), eq((byte) 99), eqStreamId(childChannel),
any(Http2Flags.class), any(ByteBuf.class), any(Promise.class));
any(Http2Flags.class), any(ByteBuf.class));
}
private Http2StreamChannel newInboundStream(int streamId, boolean endStream, final ChannelHandler childHandler) {
@ -552,14 +552,13 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
childChannel.close();
verify(frameWriter).writeRstStream(any(ChannelHandlerContext.class),
eqStreamId(childChannel), eq(Http2Error.CANCEL.code()), anyChannelPromise());
eqStreamId(childChannel), eq(Http2Error.CANCEL.code()));
}
@Test
public void outboundStreamShouldNotWriteResetFrameOnClose_IfStreamDidntExist() {
when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(),
any(Http2Headers.class), anyInt(), anyBoolean(),
any(Promise.class))).thenAnswer(new Answer<Future<Void>>() {
any(Http2Headers.class), anyInt(), anyBoolean())).thenAnswer(new Answer<Future<Void>>() {
private boolean headersWritten;
@Override
@ -568,9 +567,9 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
// refuses to allocate a new stream due to having received a GOAWAY.
if (!headersWritten) {
headersWritten = true;
return ((Promise<Void>) invocationOnMock.getArgument(5)).setFailure(new Exception("boom"));
return ImmediateEventExecutor.INSTANCE.newFailedFuture(new Exception("boom"));
}
return ((Promise<Void>) invocationOnMock.getArgument(5)).setSuccess(null);
return ImmediateEventExecutor.INSTANCE.newSucceededFuture(null);
}
});
@ -587,7 +586,7 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
childChannel.close();
// The channel was never active so we should not generate a RST frame.
verify(frameWriter, never()).writeRstStream(any(ChannelHandlerContext.class),
eqStreamId(childChannel), anyLong(), anyChannelPromise());
eqStreamId(childChannel), anyLong());
assertTrue(parentChannel.outboundMessages().isEmpty());
}
@ -603,7 +602,7 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
// A RST_STREAM frame should NOT be emitted, as we received a RST_STREAM.
verify(frameWriter, never()).writeRstStream(any(ChannelHandlerContext.class), eqStreamId(channel),
anyLong(), anyChannelPromise());
anyLong());
}
@Test
@ -633,11 +632,9 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
Http2Headers headers = new DefaultHttp2Headers();
when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(),
eq(headers), anyInt(), anyBoolean(),
any(Promise.class))).thenAnswer(invocationOnMock -> {
return ((Promise<Void>) invocationOnMock.getArgument(5)).setFailure(
new StreamException(childChannel.stream().id(), Http2Error.STREAM_CLOSED, "Stream Closed"));
});
eq(headers), anyInt(), anyBoolean())).thenAnswer(invocationOnMock ->
ImmediateEventExecutor.INSTANCE.newFailedFuture(
new StreamException(childChannel.stream().id(), Http2Error.STREAM_CLOSED, "Stream Closed")));
final Future<Void> future = childChannel.writeAndFlush(
new DefaultHttp2HeadersFrame(new DefaultHttp2Headers()));
@ -680,7 +677,7 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
// An active outbound stream should emit a RST_STREAM frame.
verify(frameWriter).writeRstStream(any(ChannelHandlerContext.class), eqStreamId(childChannel),
anyLong(), anyChannelPromise());
anyLong());
assertFalse(childChannel.isOpen());
assertFalse(childChannel.isActive());
@ -698,10 +695,8 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
Http2Headers headers = new DefaultHttp2Headers();
when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(),
eq(headers), anyInt(), anyBoolean(), any(Promise.class))).thenAnswer(invocationOnMock -> {
return ((Promise<Void>) invocationOnMock.getArgument(5)).setFailure(
new Http2NoMoreStreamIdsException());
});
eq(headers), anyInt(), anyBoolean())).thenAnswer(invocationOnMock ->
ImmediateEventExecutor.INSTANCE.newFailedFuture(new Http2NoMoreStreamIdsException()));
final Future<Void> future = childChannel.writeAndFlush(new DefaultHttp2HeadersFrame(headers));
parentChannel.flush();
@ -782,9 +777,8 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
Http2Headers headers = new DefaultHttp2Headers();
when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(),
eq(headers), anyInt(), anyBoolean(),
any(Promise.class))).thenAnswer(invocationOnMock -> {
Promise<Void> promise = invocationOnMock.getArgument(5);
eq(headers), anyInt(), anyBoolean())).thenAnswer(invocationOnMock -> {
Promise<Void> promise = ImmediateEventExecutor.INSTANCE.newPromise();
writePromises.offer(promise);
return promise;
});
@ -1305,7 +1299,7 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
assertTrue(flushSniffer.checkFlush());
verify(frameWriter, never())
.writeWindowUpdate(any(ChannelHandlerContext.class), anyInt(), anyInt(), anyChannelPromise());
.writeWindowUpdate(any(ChannelHandlerContext.class), anyInt(), anyInt());
// only the first one was read because it was legacy auto-read behavior.
verifyFramesMultiplexedToCorrectChannel(childChannel, inboundHandler, 1);
assertFalse(flushSniffer.checkFlush());
@ -1317,15 +1311,15 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
// connection will collect the bytes and decide not to send a wire level frame until more are consumed.
assertTrue(flushSniffer.checkFlush());
verify(frameWriter, never())
.writeWindowUpdate(any(ChannelHandlerContext.class), anyInt(), anyInt(), anyChannelPromise());
.writeWindowUpdate(any(ChannelHandlerContext.class), anyInt(), anyInt());
// Call read one more time which should trigger the writing of the flow control update.
childChannel.read();
verify(frameWriter)
.writeWindowUpdate(any(ChannelHandlerContext.class), eq(0), eq(32 * 1024), anyChannelPromise());
.writeWindowUpdate(any(ChannelHandlerContext.class), eq(0), eq(32 * 1024));
verify(frameWriter)
.writeWindowUpdate(any(ChannelHandlerContext.class), eq(childChannel.stream().id()),
eq(32 * 1024), anyChannelPromise());
eq(32 * 1024));
assertTrue(flushSniffer.checkFlush());
}

View File

@ -22,10 +22,9 @@ import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AsciiString;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
@ -360,50 +359,45 @@ public final class Http2TestUtil {
}).when(frameWriter).close();
when(frameWriter.configuration()).thenReturn(configuration);
when(frameWriter.writeSettings(any(ChannelHandlerContext.class), any(Http2Settings.class),
any(Promise.class))).thenAnswer((Answer<Future<Void>>) invocationOnMock ->
((Promise<Void>) invocationOnMock.getArgument(2)).setSuccess(null));
when(frameWriter.writeSettings(any(ChannelHandlerContext.class), any(Http2Settings.class)))
.thenReturn(ImmediateEventExecutor.INSTANCE.newSucceededFuture(null));
when(frameWriter.writeSettingsAck(any(ChannelHandlerContext.class), any(Promise.class)))
.thenAnswer((Answer<Future<Void>>) invocationOnMock ->
((Promise<Void>) invocationOnMock.getArgument(1)).setSuccess(null));
when(frameWriter.writeSettingsAck(any(ChannelHandlerContext.class)))
.thenReturn(ImmediateEventExecutor.INSTANCE.newSucceededFuture(null));
when(frameWriter.writePing(any(ChannelHandlerContext.class), anyBoolean(), anyLong()))
.thenReturn(ImmediateEventExecutor.INSTANCE.newSucceededFuture(null));
when(frameWriter.writeGoAway(any(ChannelHandlerContext.class), anyInt(),
anyLong(), any(ByteBuf.class), any(Promise.class))).thenAnswer(invocationOnMock -> {
anyLong(), any(ByteBuf.class))).thenAnswer(invocationOnMock -> {
buffers.offer((ByteBuf) invocationOnMock.getArgument(3));
return ((Promise<Void>) invocationOnMock.getArgument(4)).setSuccess(null);
return ImmediateEventExecutor.INSTANCE.newSucceededFuture(null);
});
when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(), any(Http2Headers.class), anyInt(),
anyBoolean(), any(Promise.class))).thenAnswer((Answer<Future<Void>>) invocationOnMock ->
((Promise<Void>) invocationOnMock.getArgument(5)).setSuccess(null));
anyBoolean())).thenReturn(ImmediateEventExecutor.INSTANCE.newSucceededFuture(null));
when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(),
any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean(),
any(Promise.class))).thenAnswer((Answer<Future<Void>>) invocationOnMock ->
((Promise<Void>) invocationOnMock.getArgument(8)).setSuccess(null));
any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean()))
.thenReturn(ImmediateEventExecutor.INSTANCE.newSucceededFuture(null));
when(frameWriter.writeData(any(ChannelHandlerContext.class), anyInt(), any(ByteBuf.class), anyInt(),
anyBoolean(), any(Promise.class))).thenAnswer(invocationOnMock -> {
anyBoolean())).thenAnswer(invocationOnMock -> {
buffers.offer((ByteBuf) invocationOnMock.getArgument(2));
return ((Promise<Void>) invocationOnMock.getArgument(5)).setSuccess(null);
return ImmediateEventExecutor.INSTANCE.newSucceededFuture(null);
});
when(frameWriter.writeRstStream(any(ChannelHandlerContext.class), anyInt(),
anyLong(), any(Promise.class))).thenAnswer((Answer<Future<Void>>) invocationOnMock ->
((Promise<Void>) invocationOnMock.getArgument(3)).setSuccess(null));
anyLong())).thenReturn(ImmediateEventExecutor.INSTANCE.newSucceededFuture(null));
when(frameWriter.writeWindowUpdate(any(ChannelHandlerContext.class), anyInt(), anyInt(),
any(Promise.class))).then((Answer<Future<Void>>) invocationOnMock ->
((Promise<Void>) invocationOnMock.getArgument(3)).setSuccess(null));
when(frameWriter.writeWindowUpdate(any(ChannelHandlerContext.class), anyInt(), anyInt()))
.thenReturn(ImmediateEventExecutor.INSTANCE.newSucceededFuture(null));
when(frameWriter.writePushPromise(any(ChannelHandlerContext.class), anyInt(), anyInt(), any(Http2Headers.class),
anyInt(), anyChannelPromise())).thenAnswer((Answer<Future<Void>>) invocationOnMock ->
((Promise<Void>) invocationOnMock.getArgument(5)).setSuccess(null));
anyInt())).thenReturn(ImmediateEventExecutor.INSTANCE.newSucceededFuture(null));
when(frameWriter.writeFrame(any(ChannelHandlerContext.class), anyByte(), anyInt(), any(Http2Flags.class),
any(ByteBuf.class), anyChannelPromise())).thenAnswer(invocationOnMock -> {
any(ByteBuf.class))).thenAnswer(invocationOnMock -> {
buffers.offer((ByteBuf) invocationOnMock.getArgument(4));
return ((Promise<Void>) invocationOnMock.getArgument(5)).setSuccess(null);
return ImmediateEventExecutor.INSTANCE.newSucceededFuture(null);
});
return frameWriter;
}

View File

@ -146,7 +146,7 @@ public class InboundHttp2ToHttpAdapterTest {
scheme(new AsciiString("https")).authority(new AsciiString("example.org"))
.path(new AsciiString("/some/path/resource2"));
runInChannel(clientChannel, () -> {
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, true, newPromiseClient());
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, true);
clientChannel.flush();
});
awaitRequests();
@ -179,7 +179,7 @@ public class InboundHttp2ToHttpAdapterTest {
.add(HttpHeaderNames.COOKIE, "c=d")
.add(HttpHeaderNames.COOKIE, "e=f");
runInChannel(clientChannel, () -> {
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, true, newPromiseClient());
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, true);
clientChannel.flush();
});
awaitRequests();
@ -211,7 +211,7 @@ public class InboundHttp2ToHttpAdapterTest {
.add(HttpHeaderNames.COOKIE, "a=b; c=d")
.add(HttpHeaderNames.COOKIE, "e=f");
runInChannel(clientChannel, () -> {
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, true, newPromiseClient());
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, true);
clientChannel.flush();
});
awaitRequests();
@ -235,7 +235,7 @@ public class InboundHttp2ToHttpAdapterTest {
.add(new AsciiString("çã".getBytes(CharsetUtil.UTF_8)),
new AsciiString("Ãã".getBytes(CharsetUtil.UTF_8)));
runInChannel(clientChannel, () -> {
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, true, newPromiseClient());
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, true);
clientChannel.flush();
});
awaitResponses();
@ -257,9 +257,8 @@ public class InboundHttp2ToHttpAdapterTest {
final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path(
new AsciiString("/some/path/resource2"));
runInChannel(clientChannel, () -> {
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
clientHandler.encoder().writeData(ctxClient(), 3, content.retainedDuplicate(), 0, true,
newPromiseClient());
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false);
clientHandler.encoder().writeData(ctxClient(), 3, content.retainedDuplicate(), 0, true);
clientChannel.flush();
});
awaitRequests();
@ -288,12 +287,12 @@ public class InboundHttp2ToHttpAdapterTest {
new AsciiString("/some/path/resource2"));
final int midPoint = text.length() / 2;
runInChannel(clientChannel, () -> {
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false);
clientHandler.encoder().writeData(
ctxClient(), 3, content.retainedSlice(0, midPoint), 0, false, newPromiseClient());
ctxClient(), 3, content.retainedSlice(0, midPoint), 0, false);
clientHandler.encoder().writeData(
ctxClient(), 3, content.retainedSlice(midPoint, text.length() - midPoint),
0, true, newPromiseClient());
0, true);
clientChannel.flush();
});
awaitRequests();
@ -321,10 +320,10 @@ public class InboundHttp2ToHttpAdapterTest {
final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path(
new AsciiString("/some/path/resource2"));
runInChannel(clientChannel, () -> {
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
clientHandler.encoder().writeData(ctxClient(), 3, content.retain(), 0, false, newPromiseClient());
clientHandler.encoder().writeData(ctxClient(), 3, content.retain(), 0, false, newPromiseClient());
clientHandler.encoder().writeData(ctxClient(), 3, content.retain(), 0, true, newPromiseClient());
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false);
clientHandler.encoder().writeData(ctxClient(), 3, content.retain(), 0, false);
clientHandler.encoder().writeData(ctxClient(), 3, content.retain(), 0, false);
clientHandler.encoder().writeData(ctxClient(), 3, content.retain(), 0, true);
clientChannel.flush();
});
awaitRequests();
@ -360,10 +359,9 @@ public class InboundHttp2ToHttpAdapterTest {
.set(new AsciiString("foo2"), new AsciiString("goo2"))
.add(new AsciiString("foo2"), new AsciiString("goo3"));
runInChannel(clientChannel, () -> {
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
clientHandler.encoder().writeData(ctxClient(), 3, content.retainedDuplicate(), 0, false,
newPromiseClient());
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers2, 0, true, newPromiseClient());
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false);
clientHandler.encoder().writeData(ctxClient(), 3, content.retainedDuplicate(), 0, false);
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers2, 0, true);
clientChannel.flush();
});
awaitRequests();
@ -402,14 +400,12 @@ public class InboundHttp2ToHttpAdapterTest {
final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(new AsciiString("PUT")).path(
new AsciiString("/some/path/resource2"));
runInChannel(clientChannel, () -> {
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false);
clientHandler.encoder().writeHeaders(ctxClient(), 5, http2Headers2, 3, (short) 123, true, 0,
false, newPromiseClient());
false);
clientChannel.flush(); // Headers are queued in the flow controller and so flush them.
clientHandler.encoder().writeData(ctxClient(), 3, content.retainedDuplicate(), 0, true,
newPromiseClient());
clientHandler.encoder().writeData(ctxClient(), 5, content2.retainedDuplicate(), 0, true,
newPromiseClient());
clientHandler.encoder().writeData(ctxClient(), 3, content.retainedDuplicate(), 0, true);
clientHandler.encoder().writeData(ctxClient(), 5, content2.retainedDuplicate(), 0, true);
clientChannel.flush();
});
awaitRequests();
@ -456,7 +452,7 @@ public class InboundHttp2ToHttpAdapterTest {
final Http2Headers http2Headers3 = new DefaultHttp2Headers().method(new AsciiString("GET"))
.path(new AsciiString("/push/test"));
runInChannel(clientChannel, () -> {
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers3, 0, true, newPromiseClient());
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers3, 0, true);
clientChannel.flush();
});
awaitRequests();
@ -475,12 +471,10 @@ public class InboundHttp2ToHttpAdapterTest {
.scheme(new AsciiString("https"))
.authority(new AsciiString("example.org"));
runInChannel(serverConnectedChannel, () -> {
serverHandler.encoder().writeHeaders(ctxServer(), 3, http2Headers, 0, false, newPromiseServer());
serverHandler.encoder().writePushPromise(ctxServer(), 3, 2, http2Headers2, 0, newPromiseServer());
serverHandler.encoder().writeData(ctxServer(), 3, content.retainedDuplicate(), 0, true,
newPromiseServer());
serverHandler.encoder().writeData(ctxServer(), 5, content2.retainedDuplicate(), 0, true,
newPromiseServer());
serverHandler.encoder().writeHeaders(ctxServer(), 3, http2Headers, 0, false);
serverHandler.encoder().writePushPromise(ctxServer(), 3, 2, http2Headers2, 0);
serverHandler.encoder().writeData(ctxServer(), 3, content.retainedDuplicate(), 0, true);
serverHandler.encoder().writeData(ctxServer(), 5, content2.retainedDuplicate(), 0, true);
serverConnectedChannel.flush();
});
awaitResponses();
@ -518,7 +512,7 @@ public class InboundHttp2ToHttpAdapterTest {
try {
runInChannel(clientChannel, () -> {
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false);
clientChannel.flush();
});
@ -528,8 +522,7 @@ public class InboundHttp2ToHttpAdapterTest {
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0);
final Http2Headers http2HeadersResponse = new DefaultHttp2Headers().status(new AsciiString("100"));
runInChannel(serverConnectedChannel, () -> {
serverHandler.encoder().writeHeaders(ctxServer(), 3, http2HeadersResponse, 0, false,
newPromiseServer());
serverHandler.encoder().writeHeaders(ctxServer(), 3, http2HeadersResponse, 0, false);
serverConnectedChannel.flush();
});
@ -538,8 +531,7 @@ public class InboundHttp2ToHttpAdapterTest {
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length());
httpHeaders.remove(HttpHeaderNames.EXPECT);
runInChannel(clientChannel, () -> {
clientHandler.encoder().writeData(ctxClient(), 3, payload.retainedDuplicate(), 0, true,
newPromiseClient());
clientHandler.encoder().writeData(ctxClient(), 3, payload.retainedDuplicate(), 0, true);
clientChannel.flush();
});
@ -551,8 +543,7 @@ public class InboundHttp2ToHttpAdapterTest {
final Http2Headers http2HeadersResponse2 = new DefaultHttp2Headers().status(new AsciiString("200"));
runInChannel(serverConnectedChannel, () -> {
serverHandler.encoder().writeHeaders(ctxServer(), 3, http2HeadersResponse2, 0, true,
newPromiseServer());
serverHandler.encoder().writeHeaders(ctxServer(), 3, http2HeadersResponse2, 0, true);
serverConnectedChannel.flush();
});
@ -586,7 +577,7 @@ public class InboundHttp2ToHttpAdapterTest {
boostrapEnv(1, 1, 2);
final Http2Settings settings = new Http2Settings().pushEnabled(true);
runInChannel(clientChannel, () -> {
clientHandler.encoder().writeSettings(ctxClient(), settings, newPromiseClient());
clientHandler.encoder().writeSettings(ctxClient(), settings);
clientChannel.flush();
});
assertTrue(settingsLatch.await(5, SECONDS));

View File

@ -106,17 +106,16 @@ public class StreamBufferingEncoderTest {
when(writer.configuration()).thenReturn(configuration);
when(configuration.frameSizePolicy()).thenReturn(frameSizePolicy);
when(frameSizePolicy.maxFrameSize()).thenReturn(DEFAULT_MAX_FRAME_SIZE);
when(writer.writeData(any(ChannelHandlerContext.class), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean(),
any(Promise.class))).thenAnswer(successAnswer());
when(writer.writeRstStream(eq(ctx), anyInt(), anyLong(), any(Promise.class))).thenAnswer(
when(writer.writeData(any(ChannelHandlerContext.class), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean()))
.thenAnswer(successAnswer());
when(writer.writeRstStream(eq(ctx), anyInt(), anyLong())).thenAnswer(
successAnswer());
when(writer.writeGoAway(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class),
any(Promise.class)))
when(writer.writeGoAway(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)))
.thenAnswer(successAnswer());
when(writer.writeHeaders(any(ChannelHandlerContext.class), anyInt(), any(Http2Headers.class),
anyInt(), anyBoolean(), any(Promise.class))).thenAnswer(noopAnswer());
anyInt(), anyBoolean())).thenAnswer(noopAnswer());
when(writer.writeHeaders(any(ChannelHandlerContext.class), anyInt(), any(Http2Headers.class),
anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean(), any(Promise.class)))
anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean()))
.thenAnswer(noopAnswer());
connection = new DefaultHttp2Connection(false);
@ -138,6 +137,13 @@ public class StreamBufferingEncoderTest {
when(channel.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT);
when(executor.inEventLoop()).thenReturn(true);
doAnswer((Answer<Promise<Void>>) invocation -> newPromise()).when(ctx).newPromise();
doAnswer((Answer<Future<Void>>) invocation -> ImmediateEventExecutor.INSTANCE.newSucceededFuture(null))
.when(ctx).newSucceededFuture();
doAnswer((Answer<Future<Void>>) invocation ->
ImmediateEventExecutor.INSTANCE.newFailedFuture(invocation.getArgument(0)))
.when(ctx).newFailedFuture(any(Throwable.class));
when(ctx.executor()).thenReturn(executor);
when(channel.isActive()).thenReturn(false);
when(channel.config()).thenReturn(config);
@ -159,34 +165,34 @@ public class StreamBufferingEncoderTest {
@Test
public void multipleWritesToActiveStream() {
encoder.writeSettingsAck(ctx, newPromise());
encoderWriteHeaders(3, newPromise());
encoder.writeSettingsAck(ctx);
encoderWriteHeaders(3);
assertEquals(0, encoder.numBufferedStreams());
ByteBuf data = data();
final int expectedBytes = data.readableBytes() * 3;
encoder.writeData(ctx, 3, data, 0, false, newPromise());
encoder.writeData(ctx, 3, data(), 0, false, newPromise());
encoder.writeData(ctx, 3, data(), 0, false, newPromise());
encoderWriteHeaders(3, newPromise());
encoder.writeData(ctx, 3, data, 0, false);
encoder.writeData(ctx, 3, data(), 0, false);
encoder.writeData(ctx, 3, data(), 0, false);
encoderWriteHeaders(3);
writeVerifyWriteHeaders(times(1), 3);
// Contiguous data writes are coalesced
ArgumentCaptor<ByteBuf> bufCaptor = ArgumentCaptor.forClass(ByteBuf.class);
verify(writer, times(1)).writeData(any(ChannelHandlerContext.class), eq(3),
bufCaptor.capture(), eq(0), eq(false), any(Promise.class));
bufCaptor.capture(), eq(0), eq(false));
assertEquals(expectedBytes, bufCaptor.getValue().readableBytes());
}
@Test
public void ensureCanCreateNextStreamWhenStreamCloses() {
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
setMaxConcurrentStreams(1);
encoderWriteHeaders(3, newPromise());
encoderWriteHeaders(3);
assertEquals(0, encoder.numBufferedStreams());
// This one gets buffered.
encoderWriteHeaders(5, newPromise());
encoderWriteHeaders(5);
assertEquals(1, connection.numActiveStreams());
assertEquals(1, encoder.numBufferedStreams());
@ -206,45 +212,44 @@ public class StreamBufferingEncoderTest {
@Test
public void alternatingWritesToActiveAndBufferedStreams() {
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
setMaxConcurrentStreams(1);
encoderWriteHeaders(3, newPromise());
encoderWriteHeaders(3);
assertEquals(0, encoder.numBufferedStreams());
encoderWriteHeaders(5, newPromise());
encoderWriteHeaders(5);
assertEquals(1, connection.numActiveStreams());
assertEquals(1, encoder.numBufferedStreams());
encoder.writeData(ctx, 3, EMPTY_BUFFER, 0, false, newPromise());
encoder.writeData(ctx, 3, EMPTY_BUFFER, 0, false);
writeVerifyWriteHeaders(times(1), 3);
encoder.writeData(ctx, 5, EMPTY_BUFFER, 0, false, newPromise());
encoder.writeData(ctx, 5, EMPTY_BUFFER, 0, false);
verify(writer, never())
.writeData(eq(ctx), eq(5), any(ByteBuf.class), eq(0), eq(false), eq(newPromise()));
.writeData(eq(ctx), eq(5), any(ByteBuf.class), eq(0), eq(false));
}
@Test
public void bufferingNewStreamFailsAfterGoAwayReceived() throws Http2Exception {
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
setMaxConcurrentStreams(0);
connection.goAwayReceived(1, 8, EMPTY_BUFFER);
Promise<Void> promise = newPromise();
encoderWriteHeaders(3, promise);
Future<Void> future = encoderWriteHeaders(3);
assertEquals(0, encoder.numBufferedStreams());
assertTrue(promise.isDone());
assertFalse(promise.isSuccess());
assertTrue(future.isDone());
assertFalse(future.isSuccess());
}
@Test
public void receivingGoAwayFailsBufferedStreams() throws Http2Exception {
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
setMaxConcurrentStreams(5);
int streamId = 3;
List<Future<Void>> futures = new ArrayList<>();
for (int i = 0; i < 9; i++) {
futures.add(encoderWriteHeaders(streamId, newPromise()));
futures.add(encoderWriteHeaders(streamId));
streamId += 2;
}
assertEquals(5, connection.numActiveStreams());
@ -266,11 +271,11 @@ public class StreamBufferingEncoderTest {
@Test
public void receivingGoAwayFailsNewStreamIfMaxConcurrentStreamsReached() throws Http2Exception {
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
setMaxConcurrentStreams(1);
encoderWriteHeaders(3, newPromise());
encoderWriteHeaders(3);
connection.goAwayReceived(11, 8, EMPTY_BUFFER);
Future<Void> f = encoderWriteHeaders(5, newPromise());
Future<Void> f = encoderWriteHeaders(5);
assertTrue(f.awaitUninterruptibly().cause() instanceof Http2GoAwayException);
assertEquals(0, encoder.numBufferedStreams());
@ -278,40 +283,41 @@ public class StreamBufferingEncoderTest {
@Test
public void sendingGoAwayShouldNotFailStreams() {
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
setMaxConcurrentStreams(1);
when(writer.writeHeaders(any(ChannelHandlerContext.class), anyInt(), any(Http2Headers.class), anyInt(),
anyBoolean(), any(Promise.class))).thenAnswer(successAnswer());
anyBoolean())).thenAnswer(successAnswer());
when(writer.writeHeaders(any(ChannelHandlerContext.class), anyInt(), any(Http2Headers.class), anyInt(),
anyShort(), anyBoolean(), anyInt(), anyBoolean(), any(Promise.class))).thenAnswer(successAnswer());
anyShort(), anyBoolean(), anyInt(), anyBoolean())).thenAnswer(successAnswer());
Future<Void> f1 = encoderWriteHeaders(3, newPromise());
Future<Void> f1 = encoderWriteHeaders(3);
assertEquals(0, encoder.numBufferedStreams());
Future<Void> f2 = encoderWriteHeaders(5, newPromise());
Future<Void> f2 = encoderWriteHeaders(5);
assertEquals(1, encoder.numBufferedStreams());
Future<Void> f3 = encoderWriteHeaders(7, newPromise());
Future<Void> f3 = encoderWriteHeaders(7);
assertEquals(2, encoder.numBufferedStreams());
ByteBuf empty = Unpooled.buffer(0);
encoder.writeGoAway(ctx, 3, CANCEL.code(), empty, newPromise());
encoder.writeGoAway(ctx, 3, CANCEL.code(), empty);
assertEquals(1, connection.numActiveStreams());
assertEquals(2, encoder.numBufferedStreams());
assertFalse(f1.isDone());
// As the first stream did exists the first future should be done.
assertTrue(f1.isDone());
assertFalse(f2.isDone());
assertFalse(f3.isDone());
}
@Test
public void endStreamDoesNotFailBufferedStream() {
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
setMaxConcurrentStreams(0);
encoderWriteHeaders(3, newPromise());
encoderWriteHeaders(3);
assertEquals(1, encoder.numBufferedStreams());
encoder.writeData(ctx, 3, EMPTY_BUFFER, 0, true, newPromise());
encoder.writeData(ctx, 3, EMPTY_BUFFER, 0, true);
assertEquals(0, connection.numActiveStreams());
assertEquals(1, encoder.numBufferedStreams());
@ -319,7 +325,7 @@ public class StreamBufferingEncoderTest {
// Simulate that we received a SETTINGS frame which
// increased MAX_CONCURRENT_STREAMS to 1.
setMaxConcurrentStreams(1);
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
assertEquals(1, connection.numActiveStreams());
assertEquals(0, encoder.numBufferedStreams());
@ -328,61 +334,60 @@ public class StreamBufferingEncoderTest {
@Test
public void rstStreamClosesBufferedStream() {
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
setMaxConcurrentStreams(0);
encoderWriteHeaders(3, newPromise());
encoderWriteHeaders(3);
assertEquals(1, encoder.numBufferedStreams());
Promise<Void> rstStreamPromise = newPromise();
encoder.writeRstStream(ctx, 3, CANCEL.code(), rstStreamPromise);
assertTrue(rstStreamPromise.isSuccess());
Future<Void> rstStreamFuture = encoder.writeRstStream(ctx, 3, CANCEL.code());
assertTrue(rstStreamFuture.isSuccess());
assertEquals(0, encoder.numBufferedStreams());
}
@Test
public void bufferUntilActiveStreamsAreReset() throws Exception {
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
setMaxConcurrentStreams(1);
encoderWriteHeaders(3, newPromise());
encoderWriteHeaders(3);
assertEquals(0, encoder.numBufferedStreams());
encoderWriteHeaders(5, newPromise());
encoderWriteHeaders(5);
assertEquals(1, encoder.numBufferedStreams());
encoderWriteHeaders(7, newPromise());
encoderWriteHeaders(7);
assertEquals(2, encoder.numBufferedStreams());
writeVerifyWriteHeaders(times(1), 3);
writeVerifyWriteHeaders(never(), 5);
writeVerifyWriteHeaders(never(), 7);
encoder.writeRstStream(ctx, 3, CANCEL.code(), newPromise());
encoder.writeRstStream(ctx, 3, CANCEL.code());
connection.remote().flowController().writePendingBytes();
writeVerifyWriteHeaders(times(1), 5);
writeVerifyWriteHeaders(never(), 7);
assertEquals(1, connection.numActiveStreams());
assertEquals(1, encoder.numBufferedStreams());
encoder.writeRstStream(ctx, 5, CANCEL.code(), newPromise());
encoder.writeRstStream(ctx, 5, CANCEL.code());
connection.remote().flowController().writePendingBytes();
writeVerifyWriteHeaders(times(1), 7);
assertEquals(1, connection.numActiveStreams());
assertEquals(0, encoder.numBufferedStreams());
encoder.writeRstStream(ctx, 7, CANCEL.code(), newPromise());
encoder.writeRstStream(ctx, 7, CANCEL.code());
assertEquals(0, connection.numActiveStreams());
assertEquals(0, encoder.numBufferedStreams());
}
@Test
public void bufferUntilMaxStreamsIncreased() {
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
setMaxConcurrentStreams(2);
encoderWriteHeaders(3, newPromise());
encoderWriteHeaders(5, newPromise());
encoderWriteHeaders(7, newPromise());
encoderWriteHeaders(9, newPromise());
encoderWriteHeaders(3);
encoderWriteHeaders(5);
encoderWriteHeaders(7);
encoderWriteHeaders(9);
assertEquals(2, encoder.numBufferedStreams());
writeVerifyWriteHeaders(times(1), 3);
@ -393,13 +398,13 @@ public class StreamBufferingEncoderTest {
// Simulate that we received a SETTINGS frame which
// increased MAX_CONCURRENT_STREAMS to 5.
setMaxConcurrentStreams(5);
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
assertEquals(0, encoder.numBufferedStreams());
writeVerifyWriteHeaders(times(1), 7);
writeVerifyWriteHeaders(times(1), 9);
encoderWriteHeaders(11, newPromise());
encoderWriteHeaders(11);
writeVerifyWriteHeaders(times(1), 11);
@ -411,7 +416,7 @@ public class StreamBufferingEncoderTest {
int initialLimit = SMALLEST_MAX_CONCURRENT_STREAMS;
int numStreams = initialLimit * 2;
for (int ix = 0, nextStreamId = 3; ix < numStreams; ++ix, nextStreamId += 2) {
encoderWriteHeaders(nextStreamId, newPromise());
encoderWriteHeaders(nextStreamId);
if (ix < initialLimit) {
writeVerifyWriteHeaders(times(1), nextStreamId);
} else {
@ -432,7 +437,7 @@ public class StreamBufferingEncoderTest {
int initialLimit = SMALLEST_MAX_CONCURRENT_STREAMS;
int numStreams = initialLimit * 2;
for (int ix = 0, nextStreamId = 3; ix < numStreams; ++ix, nextStreamId += 2) {
encoderWriteHeaders(nextStreamId, newPromise());
encoderWriteHeaders(nextStreamId);
if (ix < initialLimit) {
writeVerifyWriteHeaders(times(1), nextStreamId);
} else {
@ -452,13 +457,13 @@ public class StreamBufferingEncoderTest {
public void exhaustedStreamsDoNotBuffer() throws Http2Exception {
// Write the highest possible stream ID for the client.
// This will cause the next stream ID to be negative.
encoderWriteHeaders(Integer.MAX_VALUE, newPromise());
encoderWriteHeaders(Integer.MAX_VALUE);
// Disallow any further streams.
setMaxConcurrentStreams(0);
// Simulate numeric overflow for the next stream ID.
Future<Void> f = encoderWriteHeaders(-1, newPromise());
Future<Void> f = encoderWriteHeaders(-1);
// Verify that the write fails.
assertNotNull(f.awaitUninterruptibly().cause());
@ -466,18 +471,17 @@ public class StreamBufferingEncoderTest {
@Test
public void closedBufferedStreamReleasesByteBuf() {
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
setMaxConcurrentStreams(0);
ByteBuf data = mock(ByteBuf.class);
Future<Void> f1 = encoderWriteHeaders(3, newPromise());
Future<Void> f1 = encoderWriteHeaders(3);
assertEquals(1, encoder.numBufferedStreams());
Future<Void> f2 = encoder.writeData(ctx, 3, data, 0, false, newPromise());
Future<Void> f2 = encoder.writeData(ctx, 3, data, 0, false);
Promise<Void> rstPromise = mock(Promise.class);
encoder.writeRstStream(ctx, 3, CANCEL.code(), rstPromise);
Future<Void> rstFuture = encoder.writeRstStream(ctx, 3, CANCEL.code());
assertEquals(0, encoder.numBufferedStreams());
verify(rstPromise).setSuccess(null);
assertTrue(rstFuture.isSuccess());
assertTrue(f1.isSuccess());
assertTrue(f2.isSuccess());
verify(data).release();
@ -485,12 +489,12 @@ public class StreamBufferingEncoderTest {
@Test
public void closeShouldCancelAllBufferedStreams() throws Http2Exception {
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
connection.local().maxActiveStreams(0);
Future<Void> f1 = encoderWriteHeaders(3, newPromise());
Future<Void> f2 = encoderWriteHeaders(5, newPromise());
Future<Void> f3 = encoderWriteHeaders(7, newPromise());
Future<Void> f1 = encoderWriteHeaders(3);
Future<Void> f2 = encoderWriteHeaders(5);
Future<Void> f3 = encoderWriteHeaders(7);
encoder.close();
assertNotNull(f1.awaitUninterruptibly().cause());
@ -500,10 +504,10 @@ public class StreamBufferingEncoderTest {
@Test
public void headersAfterCloseShouldImmediatelyFail() {
encoder.writeSettingsAck(ctx, newPromise());
encoder.writeSettingsAck(ctx);
encoder.close();
Future<Void> f = encoderWriteHeaders(3, newPromise());
Future<Void> f = encoderWriteHeaders(3);
assertNotNull(f.cause());
}
@ -517,12 +521,13 @@ public class StreamBufferingEncoderTest {
}
}
private Future<Void> encoderWriteHeaders(int streamId, Promise<Void> promise) {
encoder.writeHeaders(ctx, streamId, new DefaultHttp2Headers(), 0, DEFAULT_PRIORITY_WEIGHT,
false, 0, false, promise);
private Future<Void> encoderWriteHeaders(int streamId) {
Future<Void> future =
encoder.writeHeaders(ctx, streamId, new DefaultHttp2Headers(), 0, DEFAULT_PRIORITY_WEIGHT,
false, 0, false);
try {
encoder.flowController().writePendingBytes();
return promise;
return future;
} catch (Http2Exception e) {
throw new RuntimeException(e);
}
@ -531,7 +536,7 @@ public class StreamBufferingEncoderTest {
private void writeVerifyWriteHeaders(VerificationMode mode, int streamId) {
verify(writer, mode).writeHeaders(eq(ctx), eq(streamId), any(Http2Headers.class), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0),
eq(false), any(Promise.class));
eq(false));
}
private static Answer<Future<Void>> successAnswer() {

View File

@ -88,8 +88,8 @@ public final class HelloWorldHttp2Handler extends Http2ConnectionHandler impleme
private void sendResponse(ChannelHandlerContext ctx, int streamId, ByteBuf payload) {
// Send a frame for the response status
Http2Headers headers = new DefaultHttp2Headers().status(OK.codeAsText());
encoder().writeHeaders(ctx, streamId, headers, 0, false, ctx.newPromise());
encoder().writeData(ctx, streamId, payload, 0, true, ctx.newPromise());
encoder().writeHeaders(ctx, streamId, headers, 0, false);
encoder().writeData(ctx, streamId, payload, 0, true);
// no need to call flush as channelReadComplete(...) will take care of it.
}

View File

@ -602,17 +602,8 @@ public class SslHandler extends ByteToMessageDecoder {
* {@link ChannelHandlerContext#close()}
*/
public Future<Void> closeOutbound() {
return closeOutbound(ctx.newPromise());
}
/**
* Sends an SSL {@code close_notify} message to the specified channel and
* destroys the underlying {@link SSLEngine}. This will <strong>not</strong> close the underlying
* {@link Channel}. If you want to also close the {@link Channel} use {@link Channel#close()} or
* {@link ChannelHandlerContext#close()}
*/
public Future<Void> closeOutbound(final Promise<Void> promise) {
final ChannelHandlerContext ctx = this.ctx;
Promise<Void> promise = ctx.newPromise();
if (ctx.executor().inEventLoop()) {
closeOutbound0(promise);
} else {

View File

@ -101,14 +101,14 @@ public class Http2FrameWriterDataBenchmark extends AbstractMicrobenchmark {
@Benchmark
@BenchmarkMode(Mode.AverageTime)
public void newWriter() {
writer.writeData(ctx, 3, payload.retain(), padding, true, ctx.newPromise());
writer.writeData(ctx, 3, payload.retain(), padding, true);
ctx.flush();
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
public void oldWriter() {
oldWriter.writeData(ctx, 3, payload.retain(), padding, true, ctx.newPromise());
oldWriter.writeData(ctx, 3, payload.retain(), padding, true);
ctx.flush();
}
@ -118,9 +118,9 @@ public class Http2FrameWriterDataBenchmark extends AbstractMicrobenchmark {
private final int maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
@Override
public Future<Void> writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
int padding, boolean endStream, Promise<Void> promise) {
int padding, boolean endStream) {
final Http2CodecUtil.SimpleChannelPromiseAggregator promiseAggregator =
new Http2CodecUtil.SimpleChannelPromiseAggregator(promise, ctx.executor());
new Http2CodecUtil.SimpleChannelPromiseAggregator(ctx.newPromise(), ctx.executor());
final DataFrameHeader header = new DataFrameHeader(ctx, streamId);
boolean needToReleaseHeaders = true;
boolean needToReleaseData = true;

View File

@ -88,8 +88,8 @@ public final class HelloWorldHttp2Handler extends Http2ConnectionHandler impleme
private void sendResponse(ChannelHandlerContext ctx, int streamId, ByteBuf payload) {
// Send a frame for the response status
Http2Headers headers = new DefaultHttp2Headers().status(OK.codeAsText());
encoder().writeHeaders(ctx, streamId, headers, 0, false, ctx.newPromise());
encoder().writeData(ctx, streamId, payload, 0, true, ctx.newPromise());
encoder().writeHeaders(ctx, streamId, headers, 0, false);
encoder().writeData(ctx, streamId, payload, 0, true);
// no need to call flush as channelReadComplete(...) will take care of it.
}