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) { public Future<Void> handshake(Channel channel) {
requireNonNull(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(); ChannelPipeline pipeline = channel.pipeline();
HttpResponseDecoder decoder = pipeline.get(HttpResponseDecoder.class); HttpResponseDecoder decoder = pipeline.get(HttpResponseDecoder.class);
if (decoder == null) { if (decoder == null) {
HttpClientCodec codec = pipeline.get(HttpClientCodec.class); HttpClientCodec codec = pipeline.get(HttpClientCodec.class);
if (codec == null) { if (codec == null) {
promise.setFailure(new IllegalStateException("ChannelPipeline does not contain " + return channel.newFailedFuture(new IllegalStateException("ChannelPipeline does not contain " +
"an HttpResponseDecoder or HttpClientCodec")); "an HttpResponseDecoder or HttpClientCodec"));
return promise;
} }
} }
FullHttpRequest request = newHandshakeRequest(); FullHttpRequest request = newHandshakeRequest();
Promise<Void> promise = channel.newPromise();
channel.writeAndFlush(request).addListener(channel, (ch, future) -> { channel.writeAndFlush(request).addListener(channel, (ch, future) -> {
if (future.isSuccess()) { if (future.isSuccess()) {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
@ -385,29 +373,12 @@ public abstract class WebSocketClientHandshaker {
* the {@link Future} which is notified once the handshake completes. * the {@link Future} which is notified once the handshake completes.
*/ */
public final Future<Void> processHandshake(final Channel channel, HttpResponse response) { 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) { if (response instanceof FullHttpResponse) {
try { try {
finishHandshake(channel, (FullHttpResponse) response); finishHandshake(channel, (FullHttpResponse) response);
promise.setSuccess(null); return channel.newSucceededFuture();
} catch (Throwable cause) { } catch (Throwable cause) {
promise.setFailure(cause); return channel.newFailedFuture(cause);
} }
} else { } else {
ChannelPipeline p = channel.pipeline(); ChannelPipeline p = channel.pipeline();
@ -415,10 +386,12 @@ public abstract class WebSocketClientHandshaker {
if (ctx == null) { if (ctx == null) {
ctx = p.context(HttpClientCodec.class); ctx = p.context(HttpClientCodec.class);
if (ctx == null) { 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")); "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 // 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. // then enough for the websockets handshake payload.
// //
@ -459,8 +432,8 @@ public abstract class WebSocketClientHandshaker {
} catch (Throwable cause) { } catch (Throwable cause) {
promise.setFailure(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 * The {@link Future} which is notified once the opening handshake completes
*/ */
public Future<Void> handshake(Channel channel, FullHttpRequest req) { 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 * HTTP Request
* @param responseHeaders * @param responseHeaders
* Extra headers to add to the handshake response or {@code null} if no extra headers should be added * 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 * @return future
* the {@link Future} which is notified when the opening handshake is done * the {@link Future} which is notified when the opening handshake is done
*/ */
public final Future<Void> handshake(Channel channel, FullHttpRequest req, public final Future<Void> handshake(Channel channel, FullHttpRequest req, HttpHeaders responseHeaders) {
HttpHeaders responseHeaders, final Promise<Void> promise) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("{} WebSocket version {} server handshake", channel, version()); logger.debug("{} WebSocket version {} server handshake", channel, version());
@ -208,9 +205,8 @@ public abstract class WebSocketServerHandshaker {
// this means the user use an HttpServerCodec // this means the user use an HttpServerCodec
ctx = p.context(HttpServerCodec.class); ctx = p.context(HttpServerCodec.class);
if (ctx == null) { if (ctx == null) {
promise.setFailure( return channel.newFailedFuture(
new IllegalStateException("No HttpDecoder and no HttpServerCodec in the pipeline")); new IllegalStateException("No HttpDecoder and no HttpServerCodec in the pipeline"));
return promise;
} }
p.addBefore(ctx.name(), "wsencoder", newWebSocketEncoder()); p.addBefore(ctx.name(), "wsencoder", newWebSocketEncoder());
p.addBefore(ctx.name(), "wsdecoder", newWebsocketDecoder()); p.addBefore(ctx.name(), "wsdecoder", newWebsocketDecoder());
@ -221,16 +217,12 @@ public abstract class WebSocketServerHandshaker {
encoderName = p.context(HttpResponseEncoder.class).name(); encoderName = p.context(HttpResponseEncoder.class).name();
p.addBefore(encoderName, "wsencoder", newWebSocketEncoder()); p.addBefore(encoderName, "wsencoder", newWebSocketEncoder());
} }
channel.writeAndFlush(response).addListener(channel, (ch, future) -> { return channel.writeAndFlush(response).addListener(channel, (ch, future) -> {
if (future.isSuccess()) { if (future.isSuccess()) {
ChannelPipeline p1 = ch.pipeline(); ChannelPipeline p1 = ch.pipeline();
p1.remove(encoderName); 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 * The {@link Future} which is notified once the opening handshake completes
*/ */
public Future<Void> handshake(Channel channel, HttpRequest req) { 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 * HTTP Request
* @param responseHeaders * @param responseHeaders
* Extra headers to add to the handshake response or {@code null} if no extra headers should be added * 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 * @return future
* the {@link Future} which is notified when the opening handshake is done * the {@link Future} which is notified when the opening handshake is done
*/ */
public final Future<Void> handshake(final Channel channel, HttpRequest req, public final Future<Void> handshake(final Channel channel, HttpRequest req,
final HttpHeaders responseHeaders, final Promise<Void> promise) { final HttpHeaders responseHeaders) {
if (req instanceof FullHttpRequest) { if (req instanceof FullHttpRequest) {
return handshake(channel, (FullHttpRequest) req, responseHeaders, promise); return handshake(channel, (FullHttpRequest) req, responseHeaders);
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("{} WebSocket version {} server handshake", channel, version()); logger.debug("{} WebSocket version {} server handshake", channel, version());
@ -279,11 +269,12 @@ public abstract class WebSocketServerHandshaker {
// this means the user use an HttpServerCodec // this means the user use an HttpServerCodec
ctx = p.context(HttpServerCodec.class); ctx = p.context(HttpServerCodec.class);
if (ctx == null) { if (ctx == null) {
promise.setFailure( return channel.newFailedFuture(
new IllegalStateException("No HttpDecoder and no HttpServerCodec in the pipeline")); 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 // 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. // enough for the websockets handshake payload.
// //
@ -295,7 +286,7 @@ public abstract class WebSocketServerHandshaker {
protected void messageReceived(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { protected void messageReceived(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
// Remove ourself and do the actual handshake // Remove ourself and do the actual handshake
ctx.pipeline().remove(this); ctx.pipeline().remove(this);
handshake(channel, msg, responseHeaders, promise); handshake(channel, msg, responseHeaders);
} }
@Override @Override

View File

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

View File

@ -143,12 +143,12 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
@Override @Override
public Future<Void> writeData(final ChannelHandlerContext ctx, final int streamId, ByteBuf data, int padding, 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 Http2Stream stream = connection().stream(streamId);
final EmbeddedChannel channel = stream == null ? null : (EmbeddedChannel) stream.getProperty(propertyKey); final EmbeddedChannel channel = stream == null ? null : (EmbeddedChannel) stream.getProperty(propertyKey);
if (channel == null) { if (channel == null) {
// The compressor may be null if no compatible encoding type was found in this stream's headers // 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 { try {
@ -161,13 +161,13 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
buf = nextReadableBuf(channel); buf = nextReadableBuf(channel);
} }
return super.writeData(ctx, streamId, buf == null ? Unpooled.EMPTY_BUFFER : buf, padding, 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. // END_STREAM is not set and the assumption is data is still forthcoming.
promise.setSuccess(null); return ctx.newSucceededFuture();
return promise;
} }
Promise<Void> promise = ctx.newPromise();
PromiseCombiner combiner = new PromiseCombiner(ctx.executor()); PromiseCombiner combiner = new PromiseCombiner(ctx.executor());
for (;;) { for (;;) {
ByteBuf nextBuf = nextReadableBuf(channel); ByteBuf nextBuf = nextReadableBuf(channel);
@ -177,9 +177,8 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
compressedEndOfStream = nextBuf == null; compressedEndOfStream = nextBuf == null;
} }
Promise<Void> bufPromise = ctx.newPromise(); Future<Void> future = super.writeData(ctx, streamId, buf, padding, compressedEndOfStream);
combiner.add(bufPromise); combiner.add(future);
super.writeData(ctx, streamId, buf, padding, compressedEndOfStream, bufPromise);
if (nextBuf == null) { if (nextBuf == null) {
break; break;
} }
@ -188,56 +187,54 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
buf = nextBuf; buf = nextBuf;
} }
combiner.finish(promise); combiner.finish(promise);
return promise;
} catch (Throwable cause) { } catch (Throwable cause) {
promise.tryFailure(cause); return ctx.newFailedFuture(cause);
} finally { } finally {
if (endOfStream) { if (endOfStream) {
cleanup(stream, channel); cleanup(stream, channel);
} }
} }
return promise;
} }
@Override @Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
boolean endStream, Promise<Void> promise) { boolean endStream) {
try { try {
// Determine if compression is required and sanitize the headers. // Determine if compression is required and sanitize the headers.
EmbeddedChannel compressor = newCompressor(ctx, headers, endStream); EmbeddedChannel compressor = newCompressor(ctx, headers, endStream);
// Write the headers and create the stream object. // 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. // After the stream object has been created, then attach the compressor as a property for data compression.
bindCompressorToStream(compressor, streamId); bindCompressorToStream(compressor, streamId);
return future; return future;
} catch (Throwable e) { } catch (Throwable e) {
promise.tryFailure(e); return ctx.newFailedFuture(e);
} }
return promise;
} }
@Override @Override
public Future<Void> writeHeaders(final ChannelHandlerContext ctx, final int streamId, final Http2Headers headers, 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 int streamDependency, final short weight, final boolean exclusive, final int padding,
final boolean endOfStream, final Promise<Void> promise) { final boolean endOfStream) {
try { try {
// Determine if compression is required and sanitize the headers. // Determine if compression is required and sanitize the headers.
EmbeddedChannel compressor = newCompressor(ctx, headers, endOfStream); EmbeddedChannel compressor = newCompressor(ctx, headers, endOfStream);
// Write the headers and create the stream object. // Write the headers and create the stream object.
Future<Void> future = super.writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive, 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. // After the stream object has been created, then attach the compressor as a property for data compression.
bindCompressorToStream(compressor, streamId); bindCompressorToStream(compressor, streamId);
return future; return future;
} catch (Throwable e) { } 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.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.UnstableApi; import io.netty.util.internal.UnstableApi;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
@ -35,73 +34,70 @@ public class DecoratingHttp2FrameWriter implements Http2FrameWriter {
@Override @Override
public Future<Void> writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public Future<Void> writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endStream, Promise<Void> promise) { boolean endStream) {
return delegate.writeData(ctx, streamId, data, padding, endStream, promise); return delegate.writeData(ctx, streamId, data, padding, endStream);
} }
@Override @Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
boolean endStream, Promise<Void> promise) { boolean endStream) {
return delegate.writeHeaders(ctx, streamId, headers, padding, endStream, promise); return delegate.writeHeaders(ctx, streamId, headers, padding, endStream);
} }
@Override @Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, int streamDependency, short weight, boolean exclusive, int padding,
boolean endStream, Promise<Void> promise) { boolean endStream) {
return delegate return delegate
.writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream, promise); .writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream);
} }
@Override @Override
public Future<Void> writePriority(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight, public Future<Void> writePriority(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
boolean exclusive, Promise<Void> promise) { boolean exclusive) {
return delegate.writePriority(ctx, streamId, streamDependency, weight, exclusive, promise); return delegate.writePriority(ctx, streamId, streamDependency, weight, exclusive);
} }
@Override @Override
public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode, public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode) {
Promise<Void> promise) { return delegate.writeRstStream(ctx, streamId, errorCode);
return delegate.writeRstStream(ctx, streamId, errorCode, promise);
} }
@Override @Override
public Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings, Promise<Void> promise) { public Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings) {
return delegate.writeSettings(ctx, settings, promise); return delegate.writeSettings(ctx, settings);
} }
@Override @Override
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx, Promise<Void> promise) { public Future<Void> writeSettingsAck(ChannelHandlerContext ctx) {
return delegate.writeSettingsAck(ctx, promise); return delegate.writeSettingsAck(ctx);
} }
@Override @Override
public Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data, Promise<Void> promise) { public Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data) {
return delegate.writePing(ctx, ack, data, promise); return delegate.writePing(ctx, ack, data);
} }
@Override @Override
public Future<Void> writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId, public Future<Void> writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
Http2Headers headers, int padding, Promise<Void> promise) { Http2Headers headers, int padding) {
return delegate.writePushPromise(ctx, streamId, promisedStreamId, headers, padding, promise); return delegate.writePushPromise(ctx, streamId, promisedStreamId, headers, padding);
} }
@Override @Override
public Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData, public Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData) {
Promise<Void> promise) { return delegate.writeGoAway(ctx, lastStreamId, errorCode, debugData);
return delegate.writeGoAway(ctx, lastStreamId, errorCode, debugData, promise);
} }
@Override @Override
public Future<Void> writeWindowUpdate(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement, public Future<Void> writeWindowUpdate(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) {
Promise<Void> promise) { return delegate.writeWindowUpdate(ctx, streamId, windowSizeIncrement);
return delegate.writeWindowUpdate(ctx, streamId, windowSizeIncrement, promise);
} }
@Override @Override
public Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, public Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
ByteBuf payload, Promise<Void> promise) { ByteBuf payload) {
return delegate.writeFrame(ctx, frameType, streamId, flags, payload, promise); return delegate.writeFrame(ctx, frameType, streamId, flags, payload);
} }
@Override @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.IntObjectHashMap;
import io.netty.util.collection.IntObjectMap; import io.netty.util.collection.IntObjectMap;
import io.netty.util.collection.IntObjectMap.PrimitiveEntry; import io.netty.util.collection.IntObjectMap.PrimitiveEntry;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.UnaryPromiseNotifier; import io.netty.util.concurrent.UnaryPromiseNotifier;
import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.EmptyArrays;
@ -116,7 +115,7 @@ public class DefaultHttp2Connection implements Http2Connection {
} }
@Override @Override
public Future<Void> close(final Promise<Void> promise) { public void close(final Promise<Void> promise) {
requireNonNull(promise, "promise"); requireNonNull(promise, "promise");
// Since we allow this method to be called multiple times, we must make sure that all the promises are notified // 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. // when all streams are removed and the close operation completes.
@ -131,7 +130,7 @@ public class DefaultHttp2Connection implements Http2Connection {
} }
if (isStreamMapEmpty()) { if (isStreamMapEmpty()) {
promise.trySuccess(null); promise.trySuccess(null);
return promise; return;
} }
Iterator<PrimitiveEntry<Http2Stream>> itr = streamMap.entries().iterator(); Iterator<PrimitiveEntry<Http2Stream>> itr = streamMap.entries().iterator();
@ -162,7 +161,6 @@ public class DefaultHttp2Connection implements Http2Connection {
} }
} }
} }
return closePromise;
} }
@Override @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 // 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 // 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. // these new settings. See https://github.com/netty/netty/issues/6520.
encoder.writeSettingsAck(ctx, ctx.newPromise()); encoder.writeSettingsAck(ctx);
encoder.remoteSettings(settings); encoder.remoteSettings(settings);
} else { } else {
@ -512,7 +512,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception { public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
if (autoAckPing) { if (autoAckPing) {
// Send an ack back to the remote client. // Send an ack back to the remote client.
encoder.writePing(ctx, true, data, ctx.newPromise()); encoder.writePing(ctx, true, data);
} }
listener.onPingRead(ctx, data); listener.onPingRead(ctx, data);
} }

View File

@ -120,7 +120,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
@Override @Override
public Future<Void> writeData(final ChannelHandlerContext ctx, final int streamId, ByteBuf data, int padding, 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; final Http2Stream stream;
try { try {
stream = requireStream(streamId); stream = requireStream(streamId);
@ -136,9 +136,10 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
} }
} catch (Throwable e) { } catch (Throwable e) {
data.release(); data.release();
return promise.setFailure(e); return ctx.newFailedFuture(e);
} }
Promise<Void> promise = ctx.newPromise();
// Hand control of the frame to the flow controller. // Hand control of the frame to the flow controller.
flowController().addFlowControlled(stream, flowController().addFlowControlled(stream,
new FlowControlledData(stream, data, padding, endOfStream, promise, ctx.channel())); new FlowControlledData(stream, data, padding, endOfStream, promise, ctx.channel()));
@ -147,8 +148,8 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
@Override @Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
boolean endStream, Promise<Void> promise) { boolean endStream) {
return writeHeaders0(ctx, streamId, headers, false, 0, (short) 0, false, padding, endStream, promise); return writeHeaders0(ctx, streamId, headers, false, 0, (short) 0, false, padding, endStream);
} }
private static boolean validateHeadersSentState(Http2Stream stream, Http2Headers headers, boolean isServer, private static boolean validateHeadersSentState(Http2Stream stream, Http2Headers headers, boolean isServer,
@ -163,10 +164,9 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
@Override @Override
public Future<Void> writeHeaders(final ChannelHandlerContext ctx, final int streamId, public Future<Void> writeHeaders(final ChannelHandlerContext ctx, final int streamId,
final Http2Headers headers, final int streamDependency, final short weight, final Http2Headers headers, final int streamDependency, final short weight,
final boolean exclusive, final int padding, final boolean endOfStream, final boolean exclusive, final int padding, final boolean endOfStream) {
Promise<Void> promise) {
return writeHeaders0(ctx, streamId, headers, true, streamDependency, 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, Http2Headers headers, final boolean hasPriority,
int streamDependency, final short weight, int streamDependency, final short weight,
boolean exclusive, final int padding, boolean exclusive, final int padding,
boolean endOfStream, Promise<Void> promise) { boolean endOfStream) {
if (hasPriority) { if (hasPriority) {
return frameWriter.writeHeaders(ctx, streamId, headers, streamDependency, 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, private Future<Void> writeHeaders0(final ChannelHandlerContext ctx, final int streamId,
final Http2Headers headers, final boolean hasPriority, final Http2Headers headers, final boolean hasPriority,
final int streamDependency, final short weight, final int streamDependency, final short weight,
final boolean exclusive, final int padding, final boolean exclusive, final int padding,
final boolean endOfStream, Promise<Void> promise) { final boolean endOfStream) {
try { try {
Http2Stream stream = connection.stream(streamId); Http2Stream stream = connection.stream(streamId);
if (stream == null) { if (stream == null) {
@ -202,8 +202,8 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
stream = connection.local().createStream(streamId, /*endOfStream*/ false); stream = connection.local().createStream(streamId, /*endOfStream*/ false);
} catch (Http2Exception cause) { } catch (Http2Exception cause) {
if (connection.remote().mayHaveCreatedStream(streamId)) { if (connection.remote().mayHaveCreatedStream(streamId)) {
promise.tryFailure(new IllegalStateException("Stream no longer exists: " + streamId, cause)); return ctx.newFailedFuture(
return promise; new IllegalStateException("Stream no longer exists: " + streamId, cause));
} }
throw cause; throw cause;
} }
@ -231,7 +231,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream); boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream);
Future<Void> future = sendHeaders(frameWriter, ctx, streamId, headers, hasPriority, streamDependency, 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. // 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; return future;
} else { } else {
Promise<Void> promise = ctx.newPromise();
// Pass headers to the flow-controller so it can maintain their sequence relative to DATA frames. // Pass headers to the flow-controller so it can maintain their sequence relative to DATA frames.
flowController.addFlowControlled(stream, flowController.addFlowControlled(stream,
new FlowControlledHeaders(stream, headers, hasPriority, streamDependency, new FlowControlledHeaders(stream, headers, hasPriority, streamDependency,
@ -269,27 +270,24 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
} }
} catch (Throwable t) { } catch (Throwable t) {
lifecycleManager.onError(ctx, true, t); lifecycleManager.onError(ctx, true, t);
promise.tryFailure(t); return ctx.newFailedFuture(t);
return promise;
} }
} }
@Override @Override
public Future<Void> writePriority(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight, public Future<Void> writePriority(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
boolean exclusive, Promise<Void> promise) { boolean exclusive) {
return frameWriter.writePriority(ctx, streamId, streamDependency, weight, exclusive, promise); return frameWriter.writePriority(ctx, streamId, streamDependency, weight, exclusive);
} }
@Override @Override
public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode, public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode) {
Promise<Void> promise) {
// Delegate to the lifecycle manager for proper updating of connection state. // 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 @Override
public Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings, public Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings) {
Promise<Void> promise) {
outstandingLocalSettingsQueue.add(settings); outstandingLocalSettingsQueue.add(settings);
try { try {
Boolean pushEnabled = settings.pushEnabled(); 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"); throw connectionError(PROTOCOL_ERROR, "Server sending SETTINGS frame with ENABLE_PUSH specified");
} }
} catch (Throwable e) { } catch (Throwable e) {
return promise.setFailure(e); return ctx.newFailedFuture(e);
} }
return frameWriter.writeSettings(ctx, settings, promise); return frameWriter.writeSettings(ctx, settings);
} }
@Override @Override
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx, Promise<Void> promise) { public Future<Void> writeSettingsAck(ChannelHandlerContext ctx) {
if (outstandingRemoteSettingsQueue == null) { if (outstandingRemoteSettingsQueue == null) {
return frameWriter.writeSettingsAck(ctx, promise); return frameWriter.writeSettingsAck(ctx);
} }
Http2Settings settings = outstandingRemoteSettingsQueue.poll(); Http2Settings settings = outstandingRemoteSettingsQueue.poll();
if (settings == null) { 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")); " 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 // 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 // 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. // 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 // We create a "new promise" to make sure that status from both the write and the application are taken into
// account independently. // account independently.
@ -333,13 +332,13 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
} }
@Override @Override
public Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data, Promise<Void> promise) { public Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data) {
return frameWriter.writePing(ctx, ack, data, promise); return frameWriter.writePing(ctx, ack, data);
} }
@Override @Override
public Future<Void> writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId, public Future<Void> writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
Http2Headers headers, int padding, Promise<Void> promise) { Http2Headers headers, int padding) {
try { try {
if (connection.goAwayReceived()) { if (connection.goAwayReceived()) {
throw connectionError(PROTOCOL_ERROR, "Sending PUSH_PROMISE after GO_AWAY received."); 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. // Reserve the promised stream.
connection.local().reservePushStream(promisedStreamId, stream); connection.local().reservePushStream(promisedStreamId, stream);
Future<Void> future = frameWriter.writePushPromise(ctx, streamId, promisedStreamId, headers, padding, Future<Void> future = frameWriter.writePushPromise(ctx, streamId, promisedStreamId, headers, padding);
promise);
// Writing headers may fail during the encode state if they violate HPACK limits. // Writing headers may fail during the encode state if they violate HPACK limits.
if (future.isSuccess() || !future.isDone()) { if (future.isSuccess() || !future.isDone()) {
// This just sets internal stream state which is used elsewhere in the codec and doesn't // 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; return future;
} catch (Throwable t) { } catch (Throwable t) {
lifecycleManager.onError(ctx, true, t); lifecycleManager.onError(ctx, true, t);
promise.tryFailure(t); return ctx.newFailedFuture(t);
return promise;
} }
} }
@Override @Override
public Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData, public Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData) {
Promise<Void> promise) { return lifecycleManager.goAway(ctx, lastStreamId, errorCode, debugData);
return lifecycleManager.goAway(ctx, lastStreamId, errorCode, debugData, promise);
} }
@Override @Override
public Future<Void> writeWindowUpdate(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement, public Future<Void> writeWindowUpdate(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) {
Promise<Void> promise) { return ctx.newFailedFuture(new UnsupportedOperationException("Use the Http2[Inbound|Outbound]FlowController" +
return promise.setFailure(new UnsupportedOperationException("Use the Http2[Inbound|Outbound]FlowController" +
" objects to control window sizes")); " objects to control window sizes"));
} }
@Override @Override
public Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, public Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
ByteBuf payload, Promise<Void> promise) { ByteBuf payload) {
return frameWriter.writeFrame(ctx, frameType, streamId, flags, payload, promise); return frameWriter.writeFrame(ctx, frameType, streamId, flags, payload);
} }
@Override @Override
@ -424,7 +419,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
@Override @Override
public void consumeReceivedSettings(Http2Settings settings) { public void consumeReceivedSettings(Http2Settings settings) {
if (outstandingRemoteSettingsQueue == null) { if (outstandingRemoteSettingsQueue == null) {
outstandingRemoteSettingsQueue = new ArrayDeque<Http2Settings>(2); outstandingRemoteSettingsQueue = new ArrayDeque<>(2);
} }
outstandingRemoteSettingsQueue.add(settings); outstandingRemoteSettingsQueue.add(settings);
} }
@ -508,7 +503,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
// Write the frame(s). // Write the frame(s).
frameWriter().writeData(ctx, stream.id(), toWrite, writablePadding, frameWriter().writeData(ctx, stream.id(), toWrite, writablePadding,
endOfStream && size() == 0, writePromise); endOfStream && size() == 0).cascadeTo(writePromise);
} }
@Override @Override
@ -580,7 +575,8 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Ht
promise.addListener(this); promise.addListener(this);
Future<Void> f = sendHeaders(frameWriter, ctx, stream.id(), headers, hasPriority, streamDependency, 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. // 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. 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 // 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 @Override
public void operationComplete(Future<? extends Void> future) throws Exception { public void operationComplete(Future<? extends Void> future) {
if (future.isFailed()) { if (future.isFailed()) {
error(flowController().channelHandlerContext(), future.cause()); 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.Http2FrameWriter.Configuration;
import io.netty.handler.codec.http2.Http2HeadersEncoder.SensitivityDetector; import io.netty.handler.codec.http2.Http2HeadersEncoder.SensitivityDetector;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.UnstableApi; import io.netty.util.internal.UnstableApi;
import static io.netty.buffer.Unpooled.directBuffer; import static io.netty.buffer.Unpooled.directBuffer;
@ -133,9 +132,9 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
@Override @Override
public Future<Void> writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data, public Future<Void> writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
int padding, boolean endStream, Promise<Void> promise) { int padding, boolean endStream) {
final SimpleChannelPromiseAggregator promiseAggregator = final SimpleChannelPromiseAggregator promiseAggregator =
new SimpleChannelPromiseAggregator(promise, ctx.executor()); new SimpleChannelPromiseAggregator(ctx.newPromise(), ctx.executor());
ByteBuf frameHeader = null; ByteBuf frameHeader = null;
try { try {
verifyStreamId(streamId, STREAM_ID); 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 // 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. // necessary above because we internally allocate frameHeader.
try { try {
if (data != null) { if (data != null &&
// Check if the data was released already.
data.refCnt() > 0) {
data.release(); data.release();
} }
} finally { } finally {
@ -256,22 +257,22 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
@Override @Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, 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, return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
false, 0, (short) 0, false, promise); false, 0, (short) 0, false);
} }
@Override @Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int streamDependency, short weight, boolean exclusive, 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, return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
true, streamDependency, weight, exclusive, promise); true, streamDependency, weight, exclusive);
} }
@Override @Override
public Future<Void> writePriority(ChannelHandlerContext ctx, int streamId, public Future<Void> writePriority(ChannelHandlerContext ctx, int streamId,
int streamDependency, short weight, boolean exclusive, Promise<Void> promise) { int streamDependency, short weight, boolean exclusive) {
try { try {
verifyStreamId(streamId, STREAM_ID); verifyStreamId(streamId, STREAM_ID);
verifyStreamOrConnectionId(streamDependency, STREAM_DEPENDENCY); verifyStreamOrConnectionId(streamDependency, STREAM_DEPENDENCY);
@ -282,15 +283,14 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
buf.writeInt(exclusive ? (int) (0x80000000L | streamDependency) : streamDependency); buf.writeInt(exclusive ? (int) (0x80000000L | streamDependency) : streamDependency);
// Adjust the weight so that it fits into a single byte on the wire. // Adjust the weight so that it fits into a single byte on the wire.
buf.writeByte(weight - 1); buf.writeByte(weight - 1);
return ctx.write(buf).cascadeTo(promise); return ctx.write(buf);
} catch (Throwable t) { } catch (Throwable t) {
return promise.setFailure(t); return ctx.newFailedFuture(t);
} }
} }
@Override @Override
public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode, public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode) {
Promise<Void> promise) {
try { try {
verifyStreamId(streamId, STREAM_ID); verifyStreamId(streamId, STREAM_ID);
verifyErrorCode(errorCode); verifyErrorCode(errorCode);
@ -298,15 +298,14 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
ByteBuf buf = ctx.alloc().buffer(RST_STREAM_FRAME_LENGTH); ByteBuf buf = ctx.alloc().buffer(RST_STREAM_FRAME_LENGTH);
writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, RST_STREAM, new Http2Flags(), streamId); writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, RST_STREAM, new Http2Flags(), streamId);
buf.writeInt((int) errorCode); buf.writeInt((int) errorCode);
return ctx.write(buf).cascadeTo(promise); return ctx.write(buf);
} catch (Throwable t) { } catch (Throwable t) {
return promise.setFailure(t); return ctx.newFailedFuture(t);
} }
} }
@Override @Override
public Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings, public Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings) {
Promise<Void> promise) {
try { try {
requireNonNull(settings, "settings"); requireNonNull(settings, "settings");
int payloadLength = SETTING_ENTRY_LENGTH * settings.size(); int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
@ -316,39 +315,40 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
buf.writeChar(entry.key()); buf.writeChar(entry.key());
buf.writeInt(entry.value().intValue()); buf.writeInt(entry.value().intValue());
} }
return ctx.write(buf).cascadeTo(promise); return ctx.write(buf);
} catch (Throwable t) { } catch (Throwable t) {
return promise.setFailure(t); return ctx.newFailedFuture(t);
} }
} }
@Override @Override
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx, Promise<Void> promise) { public Future<Void> writeSettingsAck(ChannelHandlerContext ctx) {
try { try {
ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH); ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
writeFrameHeaderInternal(buf, 0, SETTINGS, new Http2Flags().ack(true), 0); writeFrameHeaderInternal(buf, 0, SETTINGS, new Http2Flags().ack(true), 0);
return ctx.write(buf).cascadeTo(promise); return ctx.write(buf);
} catch (Throwable t) { } catch (Throwable t) {
return promise.setFailure(t); return ctx.newFailedFuture(t);
} }
} }
@Override @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(); Http2Flags flags = ack ? new Http2Flags().ack(true) : new Http2Flags();
ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH + PING_FRAME_PAYLOAD_LENGTH); 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 // Assume nothing below will throw until buf is written. That way we don't have to take care of ownership
// in the catch block. // in the catch block.
writeFrameHeaderInternal(buf, PING_FRAME_PAYLOAD_LENGTH, PING, flags, 0); writeFrameHeaderInternal(buf, PING_FRAME_PAYLOAD_LENGTH, PING, flags, 0);
buf.writeLong(data); buf.writeLong(data);
return ctx.write(buf).cascadeTo(promise); return ctx.write(buf);
} }
@Override @Override
public Future<Void> writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId, public Future<Void> writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
Http2Headers headers, int padding, Promise<Void> promise) { Http2Headers headers, int padding) {
ByteBuf headerBlock = null; ByteBuf headerBlock = null;
SimpleChannelPromiseAggregator promiseAggregator = new SimpleChannelPromiseAggregator(promise, ctx.executor()); SimpleChannelPromiseAggregator promiseAggregator =
new SimpleChannelPromiseAggregator(ctx.newPromise(), ctx.executor());
try { try {
verifyStreamId(streamId, STREAM_ID); verifyStreamId(streamId, STREAM_ID);
verifyStreamId(promisedStreamId, "Promised Stream ID"); verifyStreamId(promisedStreamId, "Promised Stream ID");
@ -403,8 +403,9 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
@Override @Override
public Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, public Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
ByteBuf debugData, Promise<Void> promise) { ByteBuf debugData) {
SimpleChannelPromiseAggregator promiseAggregator = new SimpleChannelPromiseAggregator(promise, ctx.executor()); SimpleChannelPromiseAggregator promiseAggregator =
new SimpleChannelPromiseAggregator(ctx.newPromise(), ctx.executor());
try { try {
verifyStreamOrConnectionId(lastStreamId, "Last Stream ID"); verifyStreamOrConnectionId(lastStreamId, "Last Stream ID");
verifyErrorCode(errorCode); verifyErrorCode(errorCode);
@ -437,7 +438,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
@Override @Override
public Future<Void> writeWindowUpdate(ChannelHandlerContext ctx, int streamId, public Future<Void> writeWindowUpdate(ChannelHandlerContext ctx, int streamId,
int windowSizeIncrement, Promise<Void> promise) { int windowSizeIncrement) {
try { try {
verifyStreamOrConnectionId(streamId, STREAM_ID); verifyStreamOrConnectionId(streamId, STREAM_ID);
verifyWindowSizeIncrement(windowSizeIncrement); verifyWindowSizeIncrement(windowSizeIncrement);
@ -445,16 +446,17 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
ByteBuf buf = ctx.alloc().buffer(WINDOW_UPDATE_FRAME_LENGTH); ByteBuf buf = ctx.alloc().buffer(WINDOW_UPDATE_FRAME_LENGTH);
writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, WINDOW_UPDATE, new Http2Flags(), streamId); writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, WINDOW_UPDATE, new Http2Flags(), streamId);
buf.writeInt(windowSizeIncrement); buf.writeInt(windowSizeIncrement);
return ctx.write(buf).cascadeTo(promise); return ctx.write(buf);
} catch (Throwable t) { } catch (Throwable t) {
return promise.setFailure(t); return ctx.newFailedFuture(t);
} }
} }
@Override @Override
public Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId, public Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
Http2Flags flags, ByteBuf payload, Promise<Void> promise) { Http2Flags flags, ByteBuf payload) {
SimpleChannelPromiseAggregator promiseAggregator = new SimpleChannelPromiseAggregator(promise, ctx.executor()); SimpleChannelPromiseAggregator promiseAggregator =
new SimpleChannelPromiseAggregator(ctx.newPromise(), ctx.executor());
try { try {
verifyStreamOrConnectionId(streamId, STREAM_ID); verifyStreamOrConnectionId(streamId, STREAM_ID);
ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH); ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
@ -481,9 +483,10 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
private Future<Void> writeHeadersInternal(ChannelHandlerContext ctx, private Future<Void> writeHeadersInternal(ChannelHandlerContext ctx,
int streamId, Http2Headers headers, int padding, boolean endStream, 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; ByteBuf headerBlock = null;
SimpleChannelPromiseAggregator promiseAggregator = new SimpleChannelPromiseAggregator(promise, ctx.executor()); SimpleChannelPromiseAggregator promiseAggregator =
new SimpleChannelPromiseAggregator(ctx.newPromise(), ctx.executor());
try { try {
verifyStreamId(streamId, STREAM_ID); verifyStreamId(streamId, STREAM_ID);
if (hasPriority) { if (hasPriority) {

View File

@ -483,7 +483,7 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
} }
// Send a window update for the stream/connection. // 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. * 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 * <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. * 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. * @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}. * 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 * Writes the given data to the internal {@link Http2FrameWriter} without performing any
* state checks on the connection/stream. * state checks on the connection/stream.
* @return
*/ */
@Override @Override
Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId, 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. // 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); .addListener(ctx.channel(), ChannelFutureListeners.CLOSE_ON_FAILURE);
if (isClient) { 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 // 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. // flushed to the OS.
// https://github.com/netty/netty/issues/5307 // 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(); ctx.flush();
Promise<Void> promise = ctx.newPromise(); Promise<Void> promise = ctx.newPromise();
doGracefulShutdown(ctx, f, promise); doGracefulShutdown(ctx, f, promise);
@ -626,7 +626,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
} }
Promise<Void> promise = ctx.newPromise(); 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) { if (http2Ex.shutdownHint() == Http2Exception.ShutdownHint.GRACEFUL_SHUTDOWN) {
doGracefulShutdown(ctx, future, promise); doGracefulShutdown(ctx, future, promise);
} else { } else {
@ -663,7 +663,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
try { try {
stream = encoder.connection().remote().createStream(streamId, true); stream = encoder.connection().remote().createStream(streamId, true);
} catch (Http2Exception e) { } catch (Http2Exception e) {
resetUnknownStream(ctx, streamId, http2Ex.error().code(), ctx.newPromise()); resetUnknownStream(ctx, streamId, http2Ex.error().code());
return; return;
} }
} }
@ -680,10 +680,10 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
if (stream == null) { if (stream == null) {
if (!outbound || connection().local().mayHaveCreatedStream(streamId)) { if (!outbound || connection().local().mayHaveCreatedStream(streamId)) {
resetUnknownStream(ctx, streamId, http2Ex.error().code(), ctx.newPromise()); resetUnknownStream(ctx, streamId, http2Ex.error().code());
} }
} else { } 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 * @param stream the Http2Stream on which the header was received
*/ */
protected void handleServerHeaderDecodeSizeError(ChannelHandlerContext ctx, Http2Stream stream) { 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() { 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 * 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. * we could create a new stream.
*/ */
private Future<Void> resetUnknownStream(final ChannelHandlerContext ctx, int streamId, long errorCode, private Future<Void> resetUnknownStream(final ChannelHandlerContext ctx, int streamId, long errorCode) {
Promise<Void> promise) { Future<Void> future = frameWriter().writeRstStream(ctx, streamId, errorCode);
Future<Void> future = frameWriter().writeRstStream(ctx, streamId, errorCode, promise);
if (future.isDone()) { if (future.isDone()) {
closeConnectionOnError(ctx, future); closeConnectionOnError(ctx, future);
} else { } else {
@ -719,21 +718,20 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
} }
@Override @Override
public Future<Void> resetStream(final ChannelHandlerContext ctx, int streamId, long errorCode, public Future<Void> resetStream(final ChannelHandlerContext ctx, int streamId, long errorCode) {
Promise<Void> promise) {
final Http2Stream stream = connection().stream(streamId); final Http2Stream stream = connection().stream(streamId);
if (stream == null) { 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, private Future<Void> resetStream(final ChannelHandlerContext ctx, final Http2Stream stream,
long errorCode, Promise<Void> promise) { long errorCode) {
if (stream.isResetSent()) { if (stream.isResetSent()) {
// Don't write a RST_STREAM frame if we have already written one. // 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 // Synchronously set the resetSent flag to prevent any subsequent calls
// from resulting in multiple reset frames being sent. // 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. // https://tools.ietf.org/html/rfc7540#section-6.4.
if (stream.state() == IDLE || if (stream.state() == IDLE ||
connection().local().created(stream) && !stream.isHeadersSent() && !stream.isPushPromiseSent()) { connection().local().created(stream) && !stream.isHeadersSent() && !stream.isPushPromiseSent()) {
future = promise.setSuccess(null); future = ctx.newSucceededFuture();
} else { } else {
future = frameWriter().writeRstStream(ctx, stream.id(), errorCode, promise); future = frameWriter().writeRstStream(ctx, stream.id(), errorCode);
} }
if (future.isDone()) { if (future.isDone()) {
processRstStreamWriteResult(ctx, stream, future); processRstStreamWriteResult(ctx, stream, future);
@ -762,24 +760,22 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
@Override @Override
public Future<Void> goAway(final ChannelHandlerContext ctx, final int lastStreamId, final long errorCode, 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(); final Http2Connection connection = connection();
try { try {
if (!connection.goAwaySent(lastStreamId, errorCode, debugData)) { if (!connection.goAwaySent(lastStreamId, errorCode, debugData)) {
debugData.release(); debugData.release();
promise.trySuccess(null); return ctx.newSucceededFuture();
return promise;
} }
} catch (Throwable cause) { } catch (Throwable cause) {
debugData.release(); debugData.release();
promise.tryFailure(cause); return ctx.newFailedFuture(cause);
return promise;
} }
// Need to retain before we write the buffer because if we do it after the refCnt could already be 0 and // 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. // result in an IllegalRefCountException.
debugData.retain(); debugData.retain();
Future<Void> future = frameWriter().writeGoAway(ctx, lastStreamId, errorCode, debugData, promise); Future<Void> future = frameWriter().writeGoAway(ctx, lastStreamId, errorCode, debugData);
if (future.isDone()) { if (future.isDone()) {
processGoAwayWriteResult(ctx, lastStreamId, errorCode, debugData, future); 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 * Close the remote endpoint with with a {@code GO_AWAY} frame. Does <strong>not</strong> flush
* immediately, this is the responsibility of the caller. * 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(); long errorCode = cause != null ? cause.error().code() : NO_ERROR.code();
int lastKnownStream; int lastKnownStream;
if (cause != null && cause.shutdownHint() == Http2Exception.ShutdownHint.HARD_SHUTDOWN) { if (cause != null && cause.shutdownHint() == Http2Exception.ShutdownHint.HARD_SHUTDOWN) {
@ -827,7 +823,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
} else { } else {
lastKnownStream = connection().remote().lastStreamCreated(); 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") @SuppressWarnings("unchecked")

View File

@ -17,7 +17,6 @@ package io.netty.handler.codec.http2;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.ObjectUtil; import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory; import io.netty.util.internal.logging.InternalLoggerFactory;
@ -49,38 +48,41 @@ final class Http2ControlFrameLimitEncoder extends DecoratingHttp2ConnectionEncod
} }
@Override @Override
public Future<Void> writeSettingsAck(ChannelHandlerContext ctx, Promise<Void> promise) { public Future<Void> writeSettingsAck(ChannelHandlerContext ctx) {
Promise<Void> newPromise = handleOutstandingControlFrames(ctx, promise); FutureListener<Void> listener = handleOutstandingControlFrames(ctx);
if (newPromise == null) { Future<Void> f = super.writeSettingsAck(ctx);
return promise; if (listener != null) {
f.addListener(listener);
} }
return super.writeSettingsAck(ctx, newPromise); return f;
} }
@Override @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. // Only apply the limit to ping acks.
if (ack) { if (ack) {
Promise<Void> newPromise = handleOutstandingControlFrames(ctx, promise); FutureListener<Void> listener = handleOutstandingControlFrames(ctx);
if (newPromise == null) { Future<Void> f = super.writePing(ctx, ack, data);
return promise; 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 @Override
public Future<Void> writeRstStream( public Future<Void> writeRstStream(
ChannelHandlerContext ctx, int streamId, long errorCode, Promise<Void> promise) { ChannelHandlerContext ctx, int streamId, long errorCode) {
Promise<Void> newPromise = handleOutstandingControlFrames(ctx, promise); FutureListener<Void> listener = handleOutstandingControlFrames(ctx);
if (newPromise == null) { Future<Void> f = super.writeRstStream(ctx, streamId, errorCode);
return promise; 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 (!limitReached) {
if (outstandingControlFrames == maxOutstandingControlFrames) { if (outstandingControlFrames == maxOutstandingControlFrames) {
// Let's try to flush once as we may be able to flush some of the control frames. // 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. // First notify the Http2LifecycleManager and then close the connection.
lifecycleManager.onError(ctx, true, exception); lifecycleManager.onError(ctx, true, exception);
ctx.close(); ctx.close();
return null;
} }
outstandingControlFrames++; outstandingControlFrames++;
// We did not reach the limit yet, add the listener to decrement the number of outstanding control frames // We did not reach the limit yet, add the listener to decrement the number of outstanding control frames
// once the promise was completed // 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 * 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. * appended to the end of the frame.
* @param endStream indicates if this is the last frame to be sent for the stream. * @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. * @return the future for the write.
*/ */
Future<Void> writeData(ChannelHandlerContext ctx, int streamId, 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 @Override
public Future<Void> write(ChannelHandlerContext ctx, Object msg) { public Future<Void> write(ChannelHandlerContext ctx, Object msg) {
Promise<Void> promise = ctx.newPromise();
if (msg instanceof Http2DataFrame) { if (msg instanceof Http2DataFrame) {
Http2DataFrame dataFrame = (Http2DataFrame) msg; Http2DataFrame dataFrame = (Http2DataFrame) msg;
encoder().writeData(ctx, dataFrame.stream().id(), dataFrame.content(), return encoder().writeData(ctx, dataFrame.stream().id(), dataFrame.content(),
dataFrame.padding(), dataFrame.isEndStream(), promise); dataFrame.padding(), dataFrame.isEndStream());
} else if (msg instanceof Http2HeadersFrame) { } else if (msg instanceof Http2HeadersFrame) {
writeHeadersFrame(ctx, (Http2HeadersFrame) msg, promise); return writeHeadersFrame(ctx, (Http2HeadersFrame) msg);
} else if (msg instanceof Http2WindowUpdateFrame) { } else if (msg instanceof Http2WindowUpdateFrame) {
Http2WindowUpdateFrame frame = (Http2WindowUpdateFrame) msg; Http2WindowUpdateFrame frame = (Http2WindowUpdateFrame) msg;
Http2FrameStream frameStream = frame.stream(); Http2FrameStream frameStream = frame.stream();
@ -300,9 +299,9 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
} else { } else {
consumeBytes(frameStream.id(), frame.windowSizeIncrement()); consumeBytes(frameStream.id(), frame.windowSizeIncrement());
} }
promise.setSuccess(null); return ctx.newSucceededFuture();
} catch (Throwable t) { } catch (Throwable t) {
promise.setFailure(t); return ctx.newFailedFuture(t);
} }
} else if (msg instanceof Http2ResetFrame) { } else if (msg instanceof Http2ResetFrame) {
Http2ResetFrame rstFrame = (Http2ResetFrame) msg; 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 // 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. // stream in an invalid state and cause a connection error.
if (connection().streamMayHaveExisted(id)) { if (connection().streamMayHaveExisted(id)) {
encoder().writeRstStream(ctx, rstFrame.stream().id(), rstFrame.errorCode(), promise); return encoder().writeRstStream(ctx, rstFrame.stream().id(), rstFrame.errorCode());
} else { } else {
ReferenceCountUtil.release(rstFrame); ReferenceCountUtil.release(rstFrame);
promise.setFailure(Http2Exception.streamError( return ctx.newFailedFuture(Http2Exception.streamError(
rstFrame.stream().id(), Http2Error.PROTOCOL_ERROR, "Stream never existed")); rstFrame.stream().id(), Http2Error.PROTOCOL_ERROR, "Stream never existed"));
} }
} else if (msg instanceof Http2PingFrame) { } else if (msg instanceof Http2PingFrame) {
Http2PingFrame frame = (Http2PingFrame) msg; 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) { } else if (msg instanceof Http2SettingsFrame) {
encoder().writeSettings(ctx, ((Http2SettingsFrame) msg).settings(), promise); return encoder().writeSettings(ctx, ((Http2SettingsFrame) msg).settings());
} else if (msg instanceof Http2SettingsAckFrame) { } else if (msg instanceof Http2SettingsAckFrame) {
// In the event of manual SETTINGS ACK, it is assumed the encoder will apply the earliest received but not // In the event of manual SETTINGS ACK, it is assumed the encoder will apply the earliest received but not
// yet ACKed settings. // yet ACKed settings.
encoder().writeSettingsAck(ctx, promise); return encoder().writeSettingsAck(ctx);
} else if (msg instanceof Http2GoAwayFrame) { } else if (msg instanceof Http2GoAwayFrame) {
writeGoAwayFrame(ctx, (Http2GoAwayFrame) msg, promise); return writeGoAwayFrame(ctx, (Http2GoAwayFrame) msg);
} else if (msg instanceof Http2PushPromiseFrame) { } else if (msg instanceof Http2PushPromiseFrame) {
Http2PushPromiseFrame pushPromiseFrame = (Http2PushPromiseFrame) msg; Http2PushPromiseFrame pushPromiseFrame = (Http2PushPromiseFrame) msg;
writePushPromise(ctx, pushPromiseFrame, promise); return writePushPromise(ctx, pushPromiseFrame);
} else if (msg instanceof Http2PriorityFrame) { } else if (msg instanceof Http2PriorityFrame) {
Http2PriorityFrame priorityFrame = (Http2PriorityFrame) msg; Http2PriorityFrame priorityFrame = (Http2PriorityFrame) msg;
encoder().writePriority(ctx, priorityFrame.stream().id(), priorityFrame.streamDependency(), return encoder().writePriority(ctx, priorityFrame.stream().id(), priorityFrame.streamDependency(),
priorityFrame.weight(), priorityFrame.exclusive(), promise); priorityFrame.weight(), priorityFrame.exclusive());
} else if (msg instanceof Http2UnknownFrame) { } else if (msg instanceof Http2UnknownFrame) {
Http2UnknownFrame unknownFrame = (Http2UnknownFrame) msg; Http2UnknownFrame unknownFrame = (Http2UnknownFrame) msg;
encoder().writeFrame(ctx, unknownFrame.frameType(), unknownFrame.stream().id(), return encoder().writeFrame(ctx, unknownFrame.frameType(), unknownFrame.stream().id(),
unknownFrame.flags(), unknownFrame.content(), promise); unknownFrame.flags(), unknownFrame.content());
} else if (!(msg instanceof Http2Frame)) { } else if (!(msg instanceof Http2Frame)) {
ctx.write(msg).cascadeTo(promise); return ctx.write(msg);
} else { } else {
ReferenceCountUtil.release(msg); ReferenceCountUtil.release(msg);
promise.setFailure(new UnsupportedMessageTypeException(msg)); return ctx.newFailedFuture(new UnsupportedMessageTypeException(msg));
} }
return promise;
} }
private void increaseInitialConnectionWindow(int deltaBytes) throws Http2Exception { private void increaseInitialConnectionWindow(int deltaBytes) throws Http2Exception {
@ -366,10 +364,10 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
return connection().local().flowController().consumeBytes(stream, bytes); 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) { if (frame.lastStreamId() > -1) {
frame.release(); 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(); int lastStreamCreated = connection().remote().lastStreamCreated();
@ -378,66 +376,72 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
if (lastStreamId > Integer.MAX_VALUE) { if (lastStreamId > Integer.MAX_VALUE) {
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, private Future<Void> writeHeadersFrame(final ChannelHandlerContext ctx, Http2HeadersFrame headersFrame) {
final Promise<Void> promise) {
if (isStreamIdValid(headersFrame.stream().id())) { if (isStreamIdValid(headersFrame.stream().id())) {
encoder().writeHeaders(ctx, headersFrame.stream().id(), headersFrame.headers(), headersFrame.padding(), return encoder().writeHeaders(ctx, headersFrame.stream().id(), headersFrame.headers(),
headersFrame.isEndStream(), promise); headersFrame.padding(), headersFrame.isEndStream());
} else if (initializeNewStream(ctx, (DefaultHttp2FrameStream) headersFrame.stream(), promise)) { } else {
final int streamId = headersFrame.stream().id(); 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(), future = encoder().writeHeaders(ctx, streamId, headersFrame.headers(), headersFrame.padding(),
headersFrame.isEndStream(), promise); headersFrame.isEndStream());
if (!promise.isDone()) { if (!future.isDone()) {
numBufferedStreams++; numBufferedStreams++;
// Clean up the stream being initialized if writing the headers fails and also // Clean up the stream being initialized if writing the headers fails and also
// decrement the number of buffered streams. // decrement the number of buffered streams.
promise.addListener(channelFuture -> { future.addListener(channelFuture -> {
numBufferedStreams--; numBufferedStreams--;
handleHeaderFuture(channelFuture, streamId); handleHeaderFuture(channelFuture, streamId);
}); });
} else { } else {
handleHeaderFuture(promise, streamId); handleHeaderFuture(future, streamId);
}
} }
return future;
} }
} }
private void writePushPromise(final ChannelHandlerContext ctx, Http2PushPromiseFrame pushPromiseFrame, private Future<Void> writePushPromise(final ChannelHandlerContext ctx, Http2PushPromiseFrame pushPromiseFrame) {
final Promise<Void> promise) {
if (isStreamIdValid(pushPromiseFrame.pushStream().id())) { if (isStreamIdValid(pushPromiseFrame.pushStream().id())) {
encoder().writePushPromise(ctx, pushPromiseFrame.stream().id(), pushPromiseFrame.pushStream().id(), return encoder().writePushPromise(ctx, pushPromiseFrame.stream().id(), pushPromiseFrame.pushStream().id(),
pushPromiseFrame.http2Headers(), pushPromiseFrame.padding(), promise); pushPromiseFrame.http2Headers(), pushPromiseFrame.padding());
} else if (initializeNewStream(ctx, (DefaultHttp2FrameStream) pushPromiseFrame.pushStream(), promise)) { } else {
final int streamId = pushPromiseFrame.stream().id(); Future<Void> future = initializeNewStream(ctx, (DefaultHttp2FrameStream) pushPromiseFrame.pushStream());
encoder().writePushPromise(ctx, streamId, pushPromiseFrame.pushStream().id(), if (future == null) {
pushPromiseFrame.http2Headers(), pushPromiseFrame.padding(), promise); final int streamId = pushPromiseFrame.stream().id();
future = encoder().writePushPromise(ctx, streamId, pushPromiseFrame.pushStream().id(),
pushPromiseFrame.http2Headers(), pushPromiseFrame.padding());
if (promise.isDone()) { if (future.isDone()) {
handleHeaderFuture(promise, streamId); handleHeaderFuture(future, streamId);
} else { } else {
numBufferedStreams++; numBufferedStreams++;
// Clean up the stream being initialized if writing the headers fails and also // Clean up the stream being initialized if writing the headers fails and also
// decrement the number of buffered streams. // decrement the number of buffered streams.
promise.addListener(f -> { future.addListener(f -> {
numBufferedStreams--; numBufferedStreams--;
handleHeaderFuture(f, streamId); handleHeaderFuture(f, streamId);
}); });
}
return future;
} }
return future;
} }
} }
private boolean initializeNewStream(ChannelHandlerContext ctx, DefaultHttp2FrameStream http2FrameStream, private Future<Void> initializeNewStream(ChannelHandlerContext ctx, DefaultHttp2FrameStream http2FrameStream) {
Promise<Void> promise) {
final Http2Connection connection = connection(); final Http2Connection connection = connection();
final int streamId = connection.local().incrementAndGetNextStreamId(); final int streamId = connection.local().incrementAndGetNextStreamId();
if (streamId < 0) { 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 // Simulate a GOAWAY being received due to stream exhaustion on this connection. We use the maximum
// valid stream ID for the current peer. // valid stream ID for the current peer.
@ -445,7 +449,7 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
Integer.MAX_VALUE - 1, NO_ERROR.code(), Integer.MAX_VALUE - 1, NO_ERROR.code(),
writeAscii(ctx.alloc(), "Stream IDs exhausted on local stream creation"))); writeAscii(ctx.alloc(), "Stream IDs exhausted on local stream creation")));
return false; return f;
} }
http2FrameStream.id = streamId; http2FrameStream.id = streamId;
@ -458,7 +462,7 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
// We should not re-use ids. // We should not re-use ids.
assert old == null; assert old == null;
return true; return null;
} }
private void handleHeaderFuture(Future<?> channelFuture, int streamId) { 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.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.UnstableApi; import io.netty.util.internal.UnstableApi;
import java.io.Closeable; 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 * @param padding additional bytes that should be added to obscure the true content size. Must be between 0 and
* 256 (inclusive). * 256 (inclusive).
* @param endStream indicates if this is the last frame to be sent for the stream. * @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. * @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: * <a href="https://tools.ietf.org/html/rfc7540#section-10.5.1">Section 10.5.1</a> states the following:
* <pre> * <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. * 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, 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. * 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 * @param padding additional bytes that should be added to obscure the true content size. Must be between 0 and
* 256 (inclusive). * 256 (inclusive).
* @param endStream indicates if this is the last frame to be sent for the stream. * @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. * @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: * <a href="https://tools.ietf.org/html/rfc7540#section-10.5.1">Section 10.5.1</a> states the following:
* <pre> * <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. * 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, Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, boolean endStream, int streamDependency, short weight, boolean exclusive, int padding, boolean endStream);
Promise<Void> promise);
/** /**
* Writes a PRIORITY frame to the remote endpoint. * Writes a PRIORITY frame to the remote endpoint.
@ -103,11 +99,10 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* depend on the connection. * depend on the connection.
* @param weight the weight for this stream. * @param weight the weight for this stream.
* @param exclusive whether this stream should be the exclusive dependant of its parent. * @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. * @return the future for the write.
*/ */
Future<Void> writePriority(ChannelHandlerContext ctx, int streamId, int streamDependency, 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. * 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 ctx the context to use for writing.
* @param streamId the stream for which to send the frame. * @param streamId the stream for which to send the frame.
* @param errorCode the error code indicating the nature of the failure. * @param errorCode the error code indicating the nature of the failure.
* @param promise the promise for the write.
* @return the future for the write. * @return the future for the write.
*/ */
Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode, Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode);
Promise<Void> promise);
/** /**
* Writes a SETTINGS frame to the remote endpoint. * Writes a SETTINGS frame to the remote endpoint.
* *
* @param ctx the context to use for writing. * @param ctx the context to use for writing.
* @param settings the settings to be sent. * @param settings the settings to be sent.
* @param promise the promise for the write.
* @return the future for the write. * @return the future for the write.
*/ */
Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings, Future<Void> writeSettings(ChannelHandlerContext ctx, Http2Settings settings);
Promise<Void> promise);
/** /**
* Writes a SETTINGS acknowledgment to the remote endpoint. * Writes a SETTINGS acknowledgment to the remote endpoint.
* *
* @param ctx the context to use for writing. * @param ctx the context to use for writing.
* @param promise the promise for the write.
* @return the future 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. * 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 * @param ack indicates whether this is an ack of a PING frame previously received from the
* remote endpoint. * remote endpoint.
* @param data the payload of the frame. * @param data the payload of the frame.
* @param promise the promise for the write.
* @return the future for the write. * @return the future for the write.
*/ */
Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data, Future<Void> writePing(ChannelHandlerContext ctx, boolean ack, long data);
Promise<Void> promise);
/** /**
* Writes a PUSH_PROMISE frame to the remote endpoint. * 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 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 * @param padding additional bytes that should be added to obscure the true content size. Must be between 0 and
* 256 (inclusive). * 256 (inclusive).
* @param promise the promise for the write.
* @return the future 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: * <a href="https://tools.ietf.org/html/rfc7540#section-10.5.1">Section 10.5.1</a> states the following:
* <pre> * <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. * 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, 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. * 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 lastStreamId the last known stream of this endpoint.
* @param errorCode the error code, if the connection was abnormally terminated. * @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 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. * @return the future for the write.
*/ */
Future<Void> writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, 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. * 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 streamId the stream for which to send the frame.
* @param windowSizeIncrement the number of bytes by which the local inbound flow control window * @param windowSizeIncrement the number of bytes by which the local inbound flow control window
* is increasing. * is increasing.
* @param promise the promise for the write.
* @return the future for the write. * @return the future for the write.
*/ */
Future<Void> writeWindowUpdate(ChannelHandlerContext ctx, int streamId, 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. * 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 streamId the stream for which to send the frame.
* @param flags the flags to write for this 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 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. * @return the future for the write.
*/ */
Future<Void> writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId, 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} * 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 ctx The context used for communication and buffer allocation if necessary.
* @param streamId The identifier of the stream to reset. * @param streamId The identifier of the stream to reset.
* @param errorCode Justification as to why this stream is being reset. See {@link Http2Error}. * @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 * @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 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. * {@code RST_STREAM} frame has been sent then the return status may indicate success immediately.
*/ */
Future<Void> resetStream(ChannelHandlerContext ctx, int streamId, long errorCode, Future<Void> resetStream(ChannelHandlerContext ctx, int streamId, long errorCode);
Promise<Void> promise);
/** /**
* Prevents the peer from creating streams and close the connection if {@code errorCode} is not * 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 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 errorCode The rational as to why the connection is being closed. See {@link Http2Error}.
* @param debugData For diagnostic purposes (carries no semantic value). * @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 * @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 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. * {@code GO_AWAY} frame has been sent then the return status may indicate success immediately.
*/ */
Future<Void> goAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, Future<Void> goAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
ByteBuf debugData, Promise<Void> promise); ByteBuf debugData);
/** /**
* Processes the given error. * Processes the given error.

View File

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

View File

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

View File

@ -154,40 +154,41 @@ public class StreamBufferingEncoder extends DecoratingHttp2ConnectionEncoder {
@Override @Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, 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, return writeHeaders(ctx, streamId, headers, 0, Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT,
false, padding, endStream, promise); false, padding, endStream);
} }
@Override @Override
public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public Future<Void> writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int streamDependency, short weight, boolean exclusive,
int padding, boolean endOfStream, Promise<Void> promise) { int padding, boolean endOfStream) {
if (closed) { if (closed) {
return promise.setFailure(new Http2ChannelClosedException()); return ctx.newFailedFuture(new Http2ChannelClosedException());
} }
if (isExistingStream(streamId) || canCreateStream()) { if (isExistingStream(streamId) || canCreateStream()) {
return super.writeHeaders(ctx, streamId, headers, streamDependency, weight, return super.writeHeaders(ctx, streamId, headers, streamDependency, weight,
exclusive, padding, endOfStream, promise); exclusive, padding, endOfStream);
} }
if (goAwayDetail != null) { if (goAwayDetail != null) {
return promise.setFailure(new Http2GoAwayException(goAwayDetail)); return ctx.newFailedFuture(new Http2GoAwayException(goAwayDetail));
} }
PendingStream pendingStream = pendingStreams.get(streamId); PendingStream pendingStream = pendingStreams.get(streamId);
if (pendingStream == null) { if (pendingStream == null) {
pendingStream = new PendingStream(ctx, streamId); pendingStream = new PendingStream(ctx, streamId);
pendingStreams.put(streamId, pendingStream); pendingStreams.put(streamId, pendingStream);
} }
Promise<Void> promise = ctx.newPromise();
pendingStream.frames.add(new HeadersFrame(headers, streamDependency, weight, exclusive, pendingStream.frames.add(new HeadersFrame(headers, streamDependency, weight, exclusive,
padding, endOfStream, promise)); padding, endOfStream, promise));
return promise; return promise;
} }
@Override @Override
public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode, public Future<Void> writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode) {
Promise<Void> promise) {
if (isExistingStream(streamId)) { 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 // Since the delegate doesn't know about any buffered streams we have to handle cancellation
// of the promises and releasing of the ByteBufs here. // 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 // about the stream anymore and thus there is not point in failing the promises and invoking
// error handling routines. // error handling routines.
stream.close(null); stream.close(null);
promise.setSuccess(null); return ctx.newSucceededFuture();
} else { } 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 @Override
public Future<Void> writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data, public Future<Void> writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
int padding, boolean endOfStream, Promise<Void> promise) { int padding, boolean endOfStream) {
if (isExistingStream(streamId)) { 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); PendingStream pendingStream = pendingStreams.get(streamId);
if (pendingStream != null) { if (pendingStream != null) {
Promise<Void> promise = ctx.newPromise();
pendingStream.frames.add(new DataFrame(data, padding, endOfStream, promise)); pendingStream.frames.add(new DataFrame(data, padding, endOfStream, promise));
return promise;
} else { } else {
ReferenceCountUtil.safeRelease(data); 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 @Override
@ -352,7 +353,8 @@ public class StreamBufferingEncoder extends DecoratingHttp2ConnectionEncoder {
@Override @Override
void send(ChannelHandlerContext ctx, int streamId) { 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 @Override
void send(ChannelHandlerContext ctx, int streamId) { 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); .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
runInChannel(clientChannel, () -> { runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, true, newPromiseClient()); clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, true);
clientHandler.flush(ctxClient()); clientHandler.flush(ctxClient());
}); });
awaitServer(); awaitServer();
@ -157,8 +157,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP); .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
runInChannel(clientChannel, () -> { runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient()); clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient()); clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient()); clientHandler.flush(ctxClient());
}); });
awaitServer(); awaitServer();
@ -178,8 +178,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP); .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
runInChannel(clientChannel, () -> { runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient()); clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient()); clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient()); clientHandler.flush(ctxClient());
}); });
awaitServer(); awaitServer();
@ -201,9 +201,9 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP); .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
runInChannel(clientChannel, () -> { runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient()); clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data1.retain(), 0, false, newPromiseClient()); clientEncoder.writeData(ctxClient(), 3, data1.retain(), 0, false);
clientEncoder.writeData(ctxClient(), 3, data2.retain(), 0, true, newPromiseClient()); clientEncoder.writeData(ctxClient(), 3, data2.retain(), 0, true);
clientHandler.flush(ctxClient()); clientHandler.flush(ctxClient());
}); });
awaitServer(); awaitServer();
@ -224,8 +224,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.BR); .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.BR);
runInChannel(clientChannel, () -> { runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient()); clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient()); clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient()); clientHandler.flush(ctxClient());
}); });
awaitServer(); awaitServer();
@ -245,8 +245,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.BR); .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.BR);
runInChannel(clientChannel, () -> { runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient()); clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient()); clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient()); clientHandler.flush(ctxClient());
}); });
awaitServer(); awaitServer();
@ -266,8 +266,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.ZSTD); .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.ZSTD);
runInChannel(clientChannel, () -> { runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient()); clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient()); clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient()); clientHandler.flush(ctxClient());
}); });
awaitServer(); awaitServer();
@ -287,8 +287,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.ZSTD); .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.ZSTD);
runInChannel(clientChannel, () -> { runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient()); clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient()); clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient()); clientHandler.flush(ctxClient());
}); });
awaitServer(); awaitServer();
@ -310,8 +310,8 @@ public class DataCompressionHttp2Test {
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.DEFLATE); .set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.DEFLATE);
runInChannel(clientChannel, () -> { runInChannel(clientChannel, () -> {
clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false, newPromiseClient()); clientEncoder.writeHeaders(ctxClient(), 3, headers, 0, false);
clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true, newPromiseClient()); clientEncoder.writeData(ctxClient(), 3, data.retain(), 0, true);
clientHandler.flush(ctxClient()); clientHandler.flush(ctxClient());
}); });
awaitServer(); awaitServer();

View File

@ -79,7 +79,6 @@ public class DefaultHttp2ConnectionDecoderTest {
private static final int STATE_RECV_TRAILERS = 1 << 1; private static final int STATE_RECV_TRAILERS = 1 << 1;
private Http2ConnectionDecoder decoder; private Http2ConnectionDecoder decoder;
private Promise<Void> promise;
@Mock @Mock
private Http2Connection connection; private Http2Connection connection;
@ -130,7 +129,7 @@ public class DefaultHttp2ConnectionDecoderTest {
public void setup() throws Exception { public void setup() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
promise = new DefaultPromise<>(ImmediateEventExecutor.INSTANCE); Promise<Void> promise = new DefaultPromise<>(ImmediateEventExecutor.INSTANCE);
final AtomicInteger headersReceivedState = new AtomicInteger(); final AtomicInteger headersReceivedState = new AtomicInteger();
when(channel.isActive()).thenReturn(true); when(channel.isActive()).thenReturn(true);
@ -209,7 +208,7 @@ public class DefaultHttp2ConnectionDecoderTest {
decode().onSettingsRead(ctx, new Http2Settings()); decode().onSettingsRead(ctx, new Http2Settings());
verify(listener).onSettingsRead(eq(ctx), eq(new Http2Settings())); verify(listener).onSettingsRead(eq(ctx), eq(new Http2Settings()));
assertTrue(decoder.prefaceReceived()); assertTrue(decoder.prefaceReceived());
verify(encoder).writeSettingsAck(eq(ctx), eq(promise)); verify(encoder).writeSettingsAck(eq(ctx));
// Simulate receiving the SETTINGS ACK for the initial settings. // Simulate receiving the SETTINGS ACK for the initial settings.
decode().onSettingsAckRead(ctx); decode().onSettingsAckRead(ctx);
@ -793,7 +792,7 @@ public class DefaultHttp2ConnectionDecoderTest {
@Test @Test
public void pingReadShouldReplyWithAck() throws Exception { public void pingReadShouldReplyWithAck() throws Exception {
decode().onPingRead(ctx, 0L); 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)); 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.channel.DefaultChannelConfig;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http2.Http2RemoteFlowController.FlowControlled; 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.Future;
import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
@ -126,25 +125,23 @@ public class DefaultHttp2ConnectionEncoderTest {
when(channel.unsafe()).thenReturn(unsafe); when(channel.unsafe()).thenReturn(unsafe);
ChannelConfig config = new DefaultChannelConfig(channel); ChannelConfig config = new DefaultChannelConfig(channel);
when(channel.config()).thenReturn(config); 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(writer.configuration()).thenReturn(writerConfig);
when(writerConfig.frameSizePolicy()).thenReturn(frameSizePolicy); when(writerConfig.frameSizePolicy()).thenReturn(frameSizePolicy);
when(frameSizePolicy.maxFrameSize()).thenReturn(64); when(frameSizePolicy.maxFrameSize()).thenReturn(64);
doAnswer((Answer<Future<Void>>) in -> ((Promise<Void>) in.getArguments()[2]) doAnswer((Answer<Future<Void>>) in -> ImmediateEventExecutor.INSTANCE.newSucceededFuture(null))
.setSuccess(null)).when(writer).writeSettings(eq(ctx), any(Http2Settings.class), any(Promise.class)); .when(writer).writeSettings(eq(ctx), any(Http2Settings.class));
doAnswer((Answer<Future<Void>>) in -> { doAnswer((Answer<Future<Void>>) in -> {
((ByteBuf) in.getArguments()[3]).release(); ((ByteBuf) in.getArguments()[3]).release();
return ((Promise<Void>) in.getArguments()[4]).setSuccess(null); return ImmediateEventExecutor.INSTANCE.newSucceededFuture(null);
}).when(writer).writeGoAway(eq(ctx), anyInt(), anyInt(), any(ByteBuf.class), any(Promise.class)); }).when(writer).writeGoAway(eq(ctx), anyInt(), anyInt(), any(ByteBuf.class));
writtenData = new ArrayList<>(); writtenData = new ArrayList<>();
writtenPadding = new ArrayList<>(); writtenPadding = new ArrayList<>();
when(writer.writeData(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean(), when(writer.writeData(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean()))
any(Promise.class))).then((Answer<Future<Void>>) in -> { .then((Answer<Future<Void>>) in -> {
// Make sure we only receive stream closure on the last frame and that void promises // Make sure we only receive stream closure on the last frame and that void promises
// are used for all writes except the last one. // are used for all writes except the last one.
Promise<Void> promise = (Promise<Void>) in.getArguments()[5];
if (streamClosed) { if (streamClosed) {
fail("Stream already closed"); fail("Stream already closed");
} else { } else {
@ -156,39 +153,41 @@ public class DefaultHttp2ConnectionEncoderTest {
// Release the buffer just as DefaultHttp2FrameWriter does // Release the buffer just as DefaultHttp2FrameWriter does
data.release(); data.release();
// Let the promise succeed to trigger listeners. // 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(), when(writer.writeHeaders(eq(ctx), anyInt(), any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(),
anyInt(), anyBoolean(), any(Promise.class))) anyInt(), anyBoolean()))
.then((Answer<Future<Void>>) invocationOnMock -> { .then((Answer<Future<Void>>) invocationOnMock -> {
Promise<Void> promise = invocationOnMock.getArgument(8);
if (streamClosed) { if (streamClosed) {
fail("Stream already closed"); fail("Stream already closed");
} else { } else {
streamClosed = (Boolean) invocationOnMock.getArguments()[5]; streamClosed = (Boolean) invocationOnMock.getArguments()[5];
} }
return promise.setSuccess(null); return ImmediateEventExecutor.INSTANCE.newSucceededFuture(null);
}); });
when(writer.writeHeaders(eq(ctx), anyInt(), any(Http2Headers.class), when(writer.writeHeaders(eq(ctx), anyInt(), any(Http2Headers.class),
anyInt(), anyBoolean(), any(Promise.class))) anyInt(), anyBoolean()))
.then((Answer<Future<Void>>) invocationOnMock -> { .then((Answer<Future<Void>>) invocationOnMock -> {
Promise<Void> promise = invocationOnMock.getArgument(5);
if (streamClosed) { if (streamClosed) {
fail("Stream already closed"); fail("Stream already closed");
} else { } else {
streamClosed = invocationOnMock.getArgument(4); streamClosed = invocationOnMock.getArgument(4);
} }
return promise.setSuccess(null); return ImmediateEventExecutor.INSTANCE.newSucceededFuture(null);
}); });
payloadCaptor = ArgumentCaptor.forClass(Http2RemoteFlowController.FlowControlled.class); payloadCaptor = ArgumentCaptor.forClass(Http2RemoteFlowController.FlowControlled.class);
doNothing().when(remoteFlow).addFlowControlled(any(Http2Stream.class), payloadCaptor.capture()); doNothing().when(remoteFlow).addFlowControlled(any(Http2Stream.class), payloadCaptor.capture());
when(ctx.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT); when(ctx.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT);
when(ctx.channel()).thenReturn(channel); when(ctx.channel()).thenReturn(channel);
doAnswer((Answer<Promise<Void>>) in -> newPromise()).when(ctx).newPromise(); doAnswer((Answer<Promise<Void>>) in -> ImmediateEventExecutor.INSTANCE.newPromise()).when(ctx).newPromise();
doAnswer((Answer<Future<Void>>) in -> newSucceededFuture()).when(ctx).newSucceededFuture(); 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(ctx.flush()).thenThrow(new AssertionFailedError("forbidden"));
when(channel.alloc()).thenReturn(PooledByteBufAllocator.DEFAULT); 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. // Use a server-side connection so we can test server push.
connection = new DefaultHttp2Connection(true); connection = new DefaultHttp2Connection(true);
connection.remote().flowController(remoteFlow); connection.remote().flowController(remoteFlow);
@ -210,8 +209,7 @@ public class DefaultHttp2ConnectionEncoderTest {
private void dataWriteShouldSignalThatFrameWasConsumedOnError0(boolean endOfStream) throws Exception { private void dataWriteShouldSignalThatFrameWasConsumedOnError0(boolean endOfStream) throws Exception {
createStream(STREAM_ID, false); createStream(STREAM_ID, false);
final ByteBuf data = dummyData(); final ByteBuf data = dummyData();
Promise<Void> p = newPromise(); Future<Void> f = encoder.writeData(ctx, STREAM_ID, data, 0, endOfStream);
encoder.writeData(ctx, STREAM_ID, data, 0, endOfStream, p);
FlowControlled controlled = payloadCaptor.getValue(); FlowControlled controlled = payloadCaptor.getValue();
assertEquals(8, controlled.size()); assertEquals(8, controlled.size());
@ -224,21 +222,20 @@ public class DefaultHttp2ConnectionEncoderTest {
assertEquals(0, controlled.size()); assertEquals(0, controlled.size());
assertEquals("abcd", writtenData.get(0)); assertEquals("abcd", writtenData.get(0));
assertEquals(0, data.refCnt()); assertEquals(0, data.refCnt());
assertSame(error, p.cause()); assertSame(error, f.cause());
} }
@Test @Test
public void dataWriteShouldSucceed() throws Exception { public void dataWriteShouldSucceed() throws Exception {
createStream(STREAM_ID, false); createStream(STREAM_ID, false);
final ByteBuf data = dummyData(); final ByteBuf data = dummyData();
Promise<Void> p = newPromise(); Future<Void> f = encoder.writeData(ctx, STREAM_ID, data, 0, true);
encoder.writeData(ctx, STREAM_ID, data, 0, true, p);
assertEquals(8, payloadCaptor.getValue().size()); assertEquals(8, payloadCaptor.getValue().size());
payloadCaptor.getValue().write(ctx, 8); payloadCaptor.getValue().write(ctx, 8);
assertEquals(0, payloadCaptor.getValue().size()); assertEquals(0, payloadCaptor.getValue().size());
assertEquals("abcdefgh", writtenData.get(0)); assertEquals("abcdefgh", writtenData.get(0));
assertEquals(0, data.refCnt()); assertEquals(0, data.refCnt());
assertTrue(p.isSuccess()); assertTrue(f.isSuccess());
} }
@Test @Test
@ -246,35 +243,33 @@ public class DefaultHttp2ConnectionEncoderTest {
createStream(STREAM_ID, false); createStream(STREAM_ID, false);
final ByteBuf data = dummyData().retain(); final ByteBuf data = dummyData().retain();
Promise<Void> promise1 = newPromise(); Future<Void> future1 = encoder.writeData(ctx, STREAM_ID, data, 0, true);
encoder.writeData(ctx, STREAM_ID, data, 0, true, promise1); Future<Void> future2 = encoder.writeData(ctx, STREAM_ID, data, 0, true);
Promise<Void> promise2 = newPromise();
encoder.writeData(ctx, STREAM_ID, data, 0, true, promise2);
// Now merge the two payloads. // Now merge the two payloads.
List<FlowControlled> capturedWrites = payloadCaptor.getAllValues(); List<FlowControlled> capturedWrites = payloadCaptor.getAllValues();
FlowControlled mergedPayload = capturedWrites.get(0); FlowControlled mergedPayload = capturedWrites.get(0);
mergedPayload.merge(ctx, capturedWrites.get(1)); mergedPayload.merge(ctx, capturedWrites.get(1));
assertEquals(16, mergedPayload.size()); assertEquals(16, mergedPayload.size());
assertFalse(promise1.isDone()); assertFalse(future1.isDone());
assertFalse(promise2.isDone()); assertFalse(future2.isDone());
// Write the merged payloads and verify it was written correctly. // Write the merged payloads and verify it was written correctly.
mergedPayload.write(ctx, 16); mergedPayload.write(ctx, 16);
assertEquals(0, mergedPayload.size()); assertEquals(0, mergedPayload.size());
assertEquals("abcdefghabcdefgh", writtenData.get(0)); assertEquals("abcdefghabcdefgh", writtenData.get(0));
assertEquals(0, data.refCnt()); assertEquals(0, data.refCnt());
assertTrue(promise1.isSuccess()); assertTrue(future1.isSuccess());
assertTrue(promise2.isSuccess()); assertTrue(future2.isSuccess());
} }
@Test @Test
public void dataFramesDontMergeWithHeaders() throws Exception { public void dataFramesDontMergeWithHeaders() throws Exception {
createStream(STREAM_ID, false); createStream(STREAM_ID, false);
final ByteBuf data = dummyData().retain(); 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); 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(); List<FlowControlled> capturedWrites = payloadCaptor.getAllValues();
assertFalse(capturedWrites.get(0).merge(ctx, capturedWrites.get(1))); assertFalse(capturedWrites.get(0).merge(ctx, capturedWrites.get(1)));
} }
@ -289,8 +284,7 @@ public class DefaultHttp2ConnectionEncoderTest {
private void assertSplitPaddingOnEmptyBuffer(ByteBuf data) throws Exception { private void assertSplitPaddingOnEmptyBuffer(ByteBuf data) throws Exception {
createStream(STREAM_ID, false); createStream(STREAM_ID, false);
when(frameSizePolicy.maxFrameSize()).thenReturn(5); when(frameSizePolicy.maxFrameSize()).thenReturn(5);
Promise<Void> p = newPromise(); Future<Void> f = encoder.writeData(ctx, STREAM_ID, data, 10, true);
encoder.writeData(ctx, STREAM_ID, data, 10, true, p);
assertEquals(10, payloadCaptor.getValue().size()); assertEquals(10, payloadCaptor.getValue().size());
payloadCaptor.getValue().write(ctx, 10); payloadCaptor.getValue().write(ctx, 10);
// writer was called 2 times // writer was called 2 times
@ -298,18 +292,17 @@ public class DefaultHttp2ConnectionEncoderTest {
assertEquals("", writtenData.get(0)); assertEquals("", writtenData.get(0));
assertEquals(10, (int) writtenPadding.get(0)); assertEquals(10, (int) writtenPadding.get(0));
assertEquals(0, data.refCnt()); assertEquals(0, data.refCnt());
assertTrue(p.isSuccess()); assertTrue(f.isSuccess());
} }
@Test @Test
public void headersWriteForUnknownStreamShouldCreateStream() throws Exception { public void headersWriteForUnknownStreamShouldCreateStream() throws Exception {
writeAllFlowControlledFrames(); writeAllFlowControlledFrames();
final int streamId = 6; final int streamId = 6;
Promise<Void> promise = newPromise(); Future<Void> f = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise);
verify(writer).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0), verify(writer).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0),
eq(false), eq(promise)); eq(false));
assertTrue(promise.isSuccess()); assertTrue(f.isSuccess());
} }
@Test @Test
@ -318,46 +311,41 @@ public class DefaultHttp2ConnectionEncoderTest {
Http2Stream parent = createStream(STREAM_ID, false); Http2Stream parent = createStream(STREAM_ID, false);
reservePushStream(PUSH_STREAM_ID, parent); reservePushStream(PUSH_STREAM_ID, parent);
Promise<Void> promise = newPromise(); encoder.writeHeaders(ctx, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false);
encoder.writeHeaders(ctx, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false, promise);
assertEquals(HALF_CLOSED_REMOTE, stream(PUSH_STREAM_ID).state()); assertEquals(HALF_CLOSED_REMOTE, stream(PUSH_STREAM_ID).state());
verify(writer).writeHeaders(eq(ctx), eq(PUSH_STREAM_ID), eq(EmptyHttp2Headers.INSTANCE), verify(writer).writeHeaders(eq(ctx), eq(PUSH_STREAM_ID), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise)); eq(0), eq(false));
} }
@Test @Test
public void trailersDoNotEndStreamThrows() { public void trailersDoNotEndStreamThrows() {
writeAllFlowControlledFrames(); writeAllFlowControlledFrames();
final int streamId = 6; final int streamId = 6;
Promise<Void> promise = newPromise(); encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise);
Promise<Void> promise2 = newPromise(); Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise2);
assertTrue(future.isDone()); assertTrue(future.isDone());
assertFalse(future.isSuccess()); assertFalse(future.isSuccess());
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise)); eq(0), eq(false));
} }
@Test @Test
public void trailersDoNotEndStreamWithDataThrows() { public void trailersDoNotEndStreamWithDataThrows() {
writeAllFlowControlledFrames(); writeAllFlowControlledFrames();
final int streamId = 6; final int streamId = 6;
Promise<Void> promise = newPromise(); encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise);
Http2Stream stream = connection.stream(streamId); Http2Stream stream = connection.stream(streamId);
when(remoteFlow.hasFlowControlled(eq(stream))).thenReturn(true); when(remoteFlow.hasFlowControlled(eq(stream))).thenReturn(true);
Promise<Void> promise2 = newPromise(); Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise2);
assertTrue(future.isDone()); assertTrue(future.isDone());
assertFalse(future.isSuccess()); assertFalse(future.isSuccess());
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise)); eq(0), eq(false));
} }
@Test @Test
@ -373,20 +361,17 @@ public class DefaultHttp2ConnectionEncoderTest {
private void tooManyHeadersThrows(boolean eos) { private void tooManyHeadersThrows(boolean eos) {
writeAllFlowControlledFrames(); writeAllFlowControlledFrames();
final int streamId = 6; final int streamId = 6;
Promise<Void> promise = newPromise(); encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise); encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, true);
Promise<Void> promise2 = newPromise();
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, true, promise2);
Promise<Void> promise3 = newPromise(); Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos);
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos, promise3);
assertTrue(future.isDone()); assertTrue(future.isDone());
assertFalse(future.isSuccess()); assertFalse(future.isSuccess());
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), 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), verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(true), eq(promise2)); eq(0), eq(true));
} }
@Test @Test
@ -414,23 +399,21 @@ public class DefaultHttp2ConnectionEncoderTest {
final int streamId = 6; final int streamId = 6;
Http2Headers infoHeaders = informationalHeaders(); Http2Headers infoHeaders = informationalHeaders();
for (int i = 0; i < infoHeaderCount; ++i) { 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);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise2);
Promise<Void> promise3 = newPromise(); Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos);
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos, promise3);
assertTrue(future.isDone()); assertTrue(future.isDone());
assertEquals(eos, future.isSuccess()); assertEquals(eos, future.isSuccess());
verify(writer, times(infoHeaderCount)).writeHeaders(eq(ctx), eq(streamId), eq(infoHeaders), 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), verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise2)); eq(0), eq(false));
if (eos) { if (eos) {
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), 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) { private void tooManyHeadersWithDataThrows(boolean eos) {
writeAllFlowControlledFrames(); writeAllFlowControlledFrames();
final int streamId = 6; final int streamId = 6;
Promise<Void> promise = newPromise(); encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise);
Http2Stream stream = connection.stream(streamId); Http2Stream stream = connection.stream(streamId);
when(remoteFlow.hasFlowControlled(eq(stream))).thenReturn(true); when(remoteFlow.hasFlowControlled(eq(stream))).thenReturn(true);
Promise<Void> promise2 = newPromise(); encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, true);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, true, promise2);
Promise<Void> promise3 = newPromise(); Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos);
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos, promise3);
assertTrue(future.isDone()); assertTrue(future.isDone());
assertFalse(future.isSuccess()); assertFalse(future.isSuccess());
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), 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), verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(true), eq(promise2)); eq(0), eq(true));
} }
@Test @Test
@ -498,27 +478,25 @@ public class DefaultHttp2ConnectionEncoderTest {
final int streamId = 6; final int streamId = 6;
Http2Headers infoHeaders = informationalHeaders(); Http2Headers infoHeaders = informationalHeaders();
for (int i = 0; i < infoHeaderCount; ++i) { 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); Http2Stream stream = connection.stream(streamId);
when(remoteFlow.hasFlowControlled(eq(stream))).thenReturn(true); when(remoteFlow.hasFlowControlled(eq(stream))).thenReturn(true);
Promise<Void> promise2 = newPromise(); encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise2);
Promise<Void> promise3 = newPromise(); Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos);
Future<Void> future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos, promise3);
assertTrue(future.isDone()); assertTrue(future.isDone());
assertEquals(eos, future.isSuccess()); assertEquals(eos, future.isSuccess());
verify(writer, times(infoHeaderCount)).writeHeaders(eq(ctx), eq(streamId), eq(infoHeaders), 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), verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise2)); eq(0), eq(false));
if (eos) { if (eos) {
verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), 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 { public void pushPromiseWriteAfterGoAwayReceivedShouldFail() throws Exception {
createStream(STREAM_ID, false); createStream(STREAM_ID, false);
goAwayReceived(0); goAwayReceived(0);
Future<Void> future = encoder.writePushPromise(ctx, STREAM_ID, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, Future<Void> future = encoder.writePushPromise(ctx, STREAM_ID, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0);
newPromise());
assertTrue(future.isDone()); assertTrue(future.isDone());
assertFalse(future.isSuccess()); assertFalse(future.isSuccess());
} }
@ -535,42 +512,38 @@ public class DefaultHttp2ConnectionEncoderTest {
@Test @Test
public void pushPromiseWriteShouldReserveStream() throws Exception { public void pushPromiseWriteShouldReserveStream() throws Exception {
createStream(STREAM_ID, false); createStream(STREAM_ID, false);
Promise<Void> promise = newPromise(); encoder.writePushPromise(ctx, STREAM_ID, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0);
encoder.writePushPromise(ctx, STREAM_ID, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, promise);
assertEquals(RESERVED_LOCAL, stream(PUSH_STREAM_ID).state()); assertEquals(RESERVED_LOCAL, stream(PUSH_STREAM_ID).state());
verify(writer).writePushPromise(eq(ctx), eq(STREAM_ID), eq(PUSH_STREAM_ID), 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 @Test
public void priorityWriteAfterGoAwayShouldSucceed() throws Exception { public void priorityWriteAfterGoAwayShouldSucceed() throws Exception {
createStream(STREAM_ID, false); createStream(STREAM_ID, false);
goAwayReceived(Integer.MAX_VALUE); goAwayReceived(Integer.MAX_VALUE);
Promise<Void> promise = newPromise(); encoder.writePriority(ctx, STREAM_ID, 0, (short) 255, true);
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));
verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(0), eq((short) 255), eq(true), eq(promise));
} }
@Test @Test
public void priorityWriteShouldSetPriorityForStream() throws Exception { public void priorityWriteShouldSetPriorityForStream() throws Exception {
Promise<Void> promise = newPromise();
short weight = 255; 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. // Verify that this did NOT create a stream object.
Http2Stream stream = stream(STREAM_ID); Http2Stream stream = stream(STREAM_ID);
assertNull(stream); 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 @Test
public void priorityWriteOnPreviouslyExistingStreamShouldSucceed() throws Exception { public void priorityWriteOnPreviouslyExistingStreamShouldSucceed() throws Exception {
createStream(STREAM_ID, false).close(); createStream(STREAM_ID, false).close();
Promise<Void> promise = newPromise();
short weight = 255; short weight = 255;
encoder.writePriority(ctx, STREAM_ID, 0, weight, true, promise); encoder.writePriority(ctx, STREAM_ID, 0, weight, true);
verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(0), eq(weight), eq(true), eq(promise)); verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(0), eq(weight), eq(true));
} }
@Test @Test
@ -579,53 +552,47 @@ public class DefaultHttp2ConnectionEncoderTest {
createStream(STREAM_ID, false); createStream(STREAM_ID, false);
createStream(parentStreamId, false).close(); createStream(parentStreamId, false).close();
Promise<Void> promise = newPromise();
short weight = 255; short weight = 255;
encoder.writePriority(ctx, STREAM_ID, parentStreamId, weight, true, promise); encoder.writePriority(ctx, STREAM_ID, parentStreamId, weight, true);
verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(parentStreamId), eq(weight), eq(true), eq(promise)); verify(writer).writePriority(eq(ctx), eq(STREAM_ID), eq(parentStreamId), eq(weight), eq(true));
} }
@Test @Test
public void rstStreamWriteForUnknownStreamShouldIgnore() throws Exception { public void rstStreamWriteForUnknownStreamShouldIgnore() throws Exception {
Promise<Void> promise = newPromise(); encoder.writeRstStream(ctx, 5, PROTOCOL_ERROR.code());
encoder.writeRstStream(ctx, 5, PROTOCOL_ERROR.code(), promise); verify(writer, never()).writeRstStream(eq(ctx), anyInt(), anyLong());
verify(writer, never()).writeRstStream(eq(ctx), anyInt(), anyLong(), eq(promise));
} }
@Test @Test
public void rstStreamShouldCloseStream() throws Exception { public void rstStreamShouldCloseStream() throws Exception {
// Create the stream and send headers. // Create the stream and send headers.
writeAllFlowControlledFrames(); 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. // Now verify that a stream reset is performed.
stream(STREAM_ID); stream(STREAM_ID);
Promise<Void> promise = newPromise(); encoder.writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code());
encoder.writeRstStream(ctx, STREAM_ID, PROTOCOL_ERROR.code(), promise); verify(lifecycleManager).resetStream(eq(ctx), eq(STREAM_ID), anyLong());
verify(lifecycleManager).resetStream(eq(ctx), eq(STREAM_ID), anyLong(), eq(promise));
} }
@Test @Test
public void pingWriteAfterGoAwayShouldSucceed() throws Exception { public void pingWriteAfterGoAwayShouldSucceed() throws Exception {
Promise<Void> promise = newPromise();
goAwayReceived(0); goAwayReceived(0);
encoder.writePing(ctx, false, 0L, promise); encoder.writePing(ctx, false, 0L);
verify(writer).writePing(eq(ctx), eq(false), eq(0L), eq(promise)); verify(writer).writePing(eq(ctx), eq(false), eq(0L));
} }
@Test @Test
public void pingWriteShouldSucceed() throws Exception { public void pingWriteShouldSucceed() throws Exception {
Promise<Void> promise = newPromise(); encoder.writePing(ctx, false, 0L);
encoder.writePing(ctx, false, 0L, promise); verify(writer).writePing(eq(ctx), eq(false), eq(0L));
verify(writer).writePing(eq(ctx), eq(false), eq(0L), eq(promise));
} }
@Test @Test
public void settingsWriteAfterGoAwayShouldSucceed() throws Exception { public void settingsWriteAfterGoAwayShouldSucceed() throws Exception {
goAwayReceived(0); goAwayReceived(0);
Promise<Void> promise = newPromise(); encoder.writeSettings(ctx, new Http2Settings());
encoder.writeSettings(ctx, new Http2Settings(), promise); verify(writer).writeSettings(eq(ctx), any(Http2Settings.class));
verify(writer).writeSettings(eq(ctx), any(Http2Settings.class), eq(promise));
} }
@Test @Test
@ -635,9 +602,8 @@ public class DefaultHttp2ConnectionEncoderTest {
settings.maxConcurrentStreams(1000); settings.maxConcurrentStreams(1000);
settings.headerTableSize(2000); settings.headerTableSize(2000);
Promise<Void> promise = newPromise(); encoder.writeSettings(ctx, settings);
encoder.writeSettings(ctx, settings, promise); verify(writer).writeSettings(eq(ctx), eq(settings));
verify(writer).writeSettings(eq(ctx), eq(settings), eq(promise));
} }
@Test @Test
@ -646,11 +612,10 @@ public class DefaultHttp2ConnectionEncoderTest {
Http2Stream stream = createStream(STREAM_ID, false); Http2Stream stream = createStream(STREAM_ID, false);
ByteBuf data = dummyData(); ByteBuf data = dummyData();
Promise<Void> promise = newPromise(); Future<Void> f = encoder.writeData(ctx, STREAM_ID, data.retain(), 0, true);
encoder.writeData(ctx, STREAM_ID, data.retain(), 0, true, promise); assertTrue(f.isSuccess());
assertTrue(promise.isSuccess());
verify(remoteFlow).addFlowControlled(eq(stream), any(FlowControlled.class)); 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)); assertEquals(data.toString(UTF_8), writtenData.get(0));
data.release(); data.release();
} }
@ -659,11 +624,10 @@ public class DefaultHttp2ConnectionEncoderTest {
public void headersWriteShouldHalfCloseStream() throws Exception { public void headersWriteShouldHalfCloseStream() throws Exception {
writeAllFlowControlledFrames(); writeAllFlowControlledFrames();
createStream(STREAM_ID, false); createStream(STREAM_ID, false);
Promise<Void> promise = newPromise(); Future<Void> f = encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true);
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true, promise);
assertTrue(promise.isSuccess()); assertTrue(f.isSuccess());
verify(lifecycleManager).closeStreamLocal(eq(stream(STREAM_ID)), eq(promise)); verify(lifecycleManager).closeStreamLocal(eq(stream(STREAM_ID)), eq(f));
} }
@Test @Test
@ -671,64 +635,54 @@ public class DefaultHttp2ConnectionEncoderTest {
writeAllFlowControlledFrames(); writeAllFlowControlledFrames();
Http2Stream parent = createStream(STREAM_ID, false); Http2Stream parent = createStream(STREAM_ID, false);
Http2Stream stream = reservePushStream(PUSH_STREAM_ID, parent); Http2Stream stream = reservePushStream(PUSH_STREAM_ID, parent);
Promise<Void> promise = newPromise(); Future<Void> f = encoder.writeHeaders(ctx, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true);
encoder.writeHeaders(ctx, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true, promise);
assertEquals(HALF_CLOSED_REMOTE, stream.state()); assertEquals(HALF_CLOSED_REMOTE, stream.state());
assertTrue(promise.isSuccess()); assertTrue(f.isSuccess());
verify(lifecycleManager).closeStreamLocal(eq(stream), eq(promise)); verify(lifecycleManager).closeStreamLocal(eq(stream), eq(f));
} }
@Test @Test
public void headersWriteShouldHalfCloseAfterOnErrorForPreCreatedStream() throws Exception { public void headersWriteShouldHalfCloseAfterOnErrorForPreCreatedStream() throws Exception {
final Promise<Void> promise = newPromise();
final Throwable ex = new RuntimeException(); final Throwable ex = new RuntimeException();
// Fake an encoding error, like HPACK's HeaderListSizeException // 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))) when(writer.writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE), eq(0), eq(true)))
.thenAnswer((Answer<Future<Void>>) invocation -> { .thenReturn(ImmediateEventExecutor.INSTANCE.newFailedFuture(ex));
promise.setFailure(ex);
return promise;
});
writeAllFlowControlledFrames(); writeAllFlowControlledFrames();
Http2Stream stream = createStream(STREAM_ID, false); 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()); assertTrue(f.isDone());
assertFalse(promise.isSuccess()); assertFalse(f.isSuccess());
assertFalse(stream.isHeadersSent()); assertFalse(stream.isHeadersSent());
InOrder inOrder = inOrder(lifecycleManager); InOrder inOrder = inOrder(lifecycleManager);
inOrder.verify(lifecycleManager).onError(eq(ctx), eq(true), eq(ex)); 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 @Test
public void headersWriteShouldHalfCloseAfterOnErrorForImplicitlyCreatedStream() throws Exception { public void headersWriteShouldHalfCloseAfterOnErrorForImplicitlyCreatedStream() throws Exception {
final Promise<Void> promise = newPromise();
final Throwable ex = new RuntimeException(); final Throwable ex = new RuntimeException();
// Fake an encoding error, like HPACK's HeaderListSizeException // 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))) when(writer.writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE), eq(0), eq(true)))
.thenAnswer((Answer<Future<Void>>) invocation -> { .thenReturn(ImmediateEventExecutor.INSTANCE.newFailedFuture(ex));
promise.setFailure(ex);
return promise;
});
writeAllFlowControlledFrames(); 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()); assertTrue(f.isDone());
assertFalse(promise.isSuccess()); assertFalse(f.isSuccess());
assertFalse(stream(STREAM_ID).isHeadersSent()); assertFalse(stream(STREAM_ID).isHeadersSent());
InOrder inOrder = inOrder(lifecycleManager); InOrder inOrder = inOrder(lifecycleManager);
inOrder.verify(lifecycleManager).onError(eq(ctx), eq(true), eq(ex)); 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 @Test
public void encoderDelegatesGoAwayToLifeCycleManager() { public void encoderDelegatesGoAwayToLifeCycleManager() {
Promise<Void> promise = newPromise(); encoder.writeGoAway(ctx, STREAM_ID, Http2Error.INTERNAL_ERROR.code(), null);
encoder.writeGoAway(ctx, STREAM_ID, Http2Error.INTERNAL_ERROR.code(), null, promise);
verify(lifecycleManager).goAway(eq(ctx), eq(STREAM_ID), eq(Http2Error.INTERNAL_ERROR.code()), verify(lifecycleManager).goAway(eq(ctx), eq(STREAM_ID), eq(Http2Error.INTERNAL_ERROR.code()),
eq((ByteBuf) null), eq(promise)); eq((ByteBuf) null));
verifyNoMoreInteractions(writer); verifyNoMoreInteractions(writer);
} }
@ -736,11 +690,10 @@ public class DefaultHttp2ConnectionEncoderTest {
public void dataWriteToClosedStreamShouldFail() throws Exception { public void dataWriteToClosedStreamShouldFail() throws Exception {
createStream(STREAM_ID, false).close(); createStream(STREAM_ID, false).close();
ByteBuf data = mock(ByteBuf.class); ByteBuf data = mock(ByteBuf.class);
Promise<Void> promise = newPromise(); Future<Void> f = encoder.writeData(ctx, STREAM_ID, data, 0, false);
encoder.writeData(ctx, STREAM_ID, data, 0, false, promise); assertTrue(f.isDone());
assertTrue(promise.isDone()); assertFalse(f.isSuccess());
assertFalse(promise.isSuccess()); assertThat(f.cause(), instanceOf(IllegalArgumentException.class));
assertThat(promise.cause(), instanceOf(IllegalArgumentException.class));
verify(data).release(); verify(data).release();
} }
@ -748,11 +701,10 @@ public class DefaultHttp2ConnectionEncoderTest {
public void dataWriteToHalfClosedLocalStreamShouldFail() throws Exception { public void dataWriteToHalfClosedLocalStreamShouldFail() throws Exception {
createStream(STREAM_ID, true); createStream(STREAM_ID, true);
ByteBuf data = mock(ByteBuf.class); ByteBuf data = mock(ByteBuf.class);
Promise<Void> promise = newPromise(); Future<Void> f = encoder.writeData(ctx, STREAM_ID, data, 0, false);
encoder.writeData(ctx, STREAM_ID, data, 0, false, promise); assertTrue(f.isDone());
assertTrue(promise.isDone()); assertFalse(f.isSuccess());
assertFalse(promise.isSuccess()); assertThat(f.cause(), instanceOf(IllegalStateException.class));
assertThat(promise.cause(), instanceOf(IllegalStateException.class));
verify(data).release(); verify(data).release();
} }
@ -761,7 +713,7 @@ public class DefaultHttp2ConnectionEncoderTest {
Http2Stream stream = createStream(STREAM_ID, false); Http2Stream stream = createStream(STREAM_ID, false);
connection.goAwaySent(0, 0, EMPTY_BUFFER); connection.goAwaySent(0, 0, EMPTY_BUFFER);
ByteBuf data = mock(ByteBuf.class); 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)); verify(remoteFlow).addFlowControlled(eq(stream), any(FlowControlled.class));
} }
@ -770,10 +722,9 @@ public class DefaultHttp2ConnectionEncoderTest {
writeAllFlowControlledFrames(); writeAllFlowControlledFrames();
createStream(STREAM_ID, false); createStream(STREAM_ID, false);
goAwaySent(0); goAwaySent(0);
Promise<Void> promise = newPromise(); encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false);
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false, promise);
verify(writer).writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE), verify(writer).writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise)); eq(0), eq(false));
} }
@Test @Test
@ -781,7 +732,7 @@ public class DefaultHttp2ConnectionEncoderTest {
Http2Stream stream = createStream(STREAM_ID, false); Http2Stream stream = createStream(STREAM_ID, false);
goAwayReceived(STREAM_ID); goAwayReceived(STREAM_ID);
ByteBuf data = mock(ByteBuf.class); 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)); verify(remoteFlow).addFlowControlled(eq(stream), any(FlowControlled.class));
} }
@ -789,31 +740,28 @@ public class DefaultHttp2ConnectionEncoderTest {
public void canWriteHeaderFrameAfterGoAwayReceived() throws Http2Exception { public void canWriteHeaderFrameAfterGoAwayReceived() throws Http2Exception {
writeAllFlowControlledFrames(); writeAllFlowControlledFrames();
goAwayReceived(STREAM_ID); goAwayReceived(STREAM_ID);
Promise<Void> promise = newPromise(); encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false);
encoder.writeHeaders(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false, promise);
verify(writer).writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE), verify(writer).writeHeaders(eq(ctx), eq(STREAM_ID), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise)); eq(0), eq(false));
} }
@Test @Test
public void headersWithNoPriority() { public void headersWithNoPriority() {
writeAllFlowControlledFrames(); writeAllFlowControlledFrames();
final int streamId = 6; final int streamId = 6;
Promise<Void> promise = newPromise(); encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false);
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise);
verify(writer).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), verify(writer).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE),
eq(0), eq(false), eq(promise)); eq(0), eq(false));
} }
@Test @Test
public void headersWithPriority() { public void headersWithPriority() {
writeAllFlowControlledFrames(); writeAllFlowControlledFrames();
final int streamId = 6; final int streamId = 6;
Promise<Void> promise = newPromise();
encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 10, DEFAULT_PRIORITY_WEIGHT, 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), 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() { private void writeAllFlowControlledFrames() {
@ -845,14 +793,6 @@ public class DefaultHttp2ConnectionEncoderTest {
connection.goAwaySent(lastStreamId, 0, EMPTY_BUFFER); 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() { private static ByteBuf dummyData() {
// The buffer is purposely 8 bytes so it will even work for a ping frame. // The buffer is purposely 8 bytes so it will even work for a ping frame.
return wrappedBuffer("abcdefgh".getBytes(UTF_8)); 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.channel.local.LocalHandler;
import io.netty.handler.codec.http2.Http2Connection.Endpoint; import io.netty.handler.codec.http2.Http2Connection.Endpoint;
import io.netty.handler.codec.http2.Http2Stream.State; 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 io.netty.util.concurrent.Promise;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
@ -157,10 +155,9 @@ public class DefaultHttp2ConnectionTest {
final Promise<Void> promise = group.next().newPromise(); final Promise<Void> promise = group.next().newPromise();
final CountDownLatch latch = new CountDownLatch(client.numActiveStreams()); final CountDownLatch latch = new CountDownLatch(client.numActiveStreams());
client.forEachActiveStream(stream -> { client.forEachActiveStream(stream -> {
client.close(promise).addListener((FutureListener<Void>) future -> { client.close(promise.addListener(future -> {
assertTrue(promise.isDone());
latch.countDown(); latch.countDown();
}); }));
return true; return true;
}); });
assertTrue(latch.await(5, TimeUnit.SECONDS)); assertTrue(latch.await(5, TimeUnit.SECONDS));
@ -186,10 +183,9 @@ public class DefaultHttp2ConnectionTest {
return true; return true;
}); });
} catch (Http2Exception ignored) { } catch (Http2Exception ignored) {
client.close(promise).addListener((FutureListener<Void>) future -> { client.close(promise.addListener(future -> {
assertTrue(promise.isDone());
latch.countDown(); latch.countDown();
}); }));
} }
assertTrue(latch.await(5, TimeUnit.SECONDS)); assertTrue(latch.await(5, TimeUnit.SECONDS));
} }
@ -636,10 +632,9 @@ public class DefaultHttp2ConnectionTest {
private void testRemoveAllStreams() throws InterruptedException { private void testRemoveAllStreams() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1);
final Promise<Void> promise = group.next().newPromise(); final Promise<Void> promise = group.next().newPromise();
client.close(promise).addListener((FutureListener<Void>) future -> { client.close(promise.addListener(future -> {
assertTrue(promise.isDone());
latch.countDown(); latch.countDown();
}); }));
assertTrue(latch.await(5, TimeUnit.SECONDS)); 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.Channel;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -50,8 +48,6 @@ public class DefaultHttp2FrameWriterTest {
private ByteBuf expectedOutbound; private ByteBuf expectedOutbound;
private Promise<Void> promise;
private Http2HeadersEncoder http2HeadersEncoder; private Http2HeadersEncoder http2HeadersEncoder;
@Mock @Mock
@ -76,8 +72,6 @@ public class DefaultHttp2FrameWriterTest {
expectedOutbound = Unpooled.EMPTY_BUFFER; expectedOutbound = Unpooled.EMPTY_BUFFER;
promise = new DefaultPromise<>(ImmediateEventExecutor.INSTANCE);
Answer<Object> answer = var1 -> { Answer<Object> answer = var1 -> {
Object msg = var1.getArgument(0); Object msg = var1.getArgument(0);
if (msg instanceof ByteBuf) { if (msg instanceof ByteBuf) {
@ -90,6 +84,7 @@ public class DefaultHttp2FrameWriterTest {
when(ctx.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT); when(ctx.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT);
when(ctx.channel()).thenReturn(channel); when(ctx.channel()).thenReturn(channel);
when(ctx.executor()).thenReturn(ImmediateEventExecutor.INSTANCE); when(ctx.executor()).thenReturn(ImmediateEventExecutor.INSTANCE);
when(ctx.newPromise()).thenReturn(ImmediateEventExecutor.INSTANCE.newPromise());
} }
@AfterEach @AfterEach
@ -105,7 +100,7 @@ public class DefaultHttp2FrameWriterTest {
Http2Headers headers = new DefaultHttp2Headers() Http2Headers headers = new DefaultHttp2Headers()
.method("GET").path("/").authority("foo.com").scheme("https"); .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[] expectedPayload = headerPayload(streamId, headers);
byte[] expectedFrameBytes = { byte[] expectedFrameBytes = {
@ -124,7 +119,7 @@ public class DefaultHttp2FrameWriterTest {
Http2Headers headers = new DefaultHttp2Headers() Http2Headers headers = new DefaultHttp2Headers()
.method("GET").path("/").authority("foo.com").scheme("https"); .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[] expectedPayload = headerPayload(streamId, headers, (byte) 4);
byte[] expectedFrameBytes = { byte[] expectedFrameBytes = {
@ -143,7 +138,7 @@ public class DefaultHttp2FrameWriterTest {
Http2Headers headers = new DefaultHttp2Headers() Http2Headers headers = new DefaultHttp2Headers()
.method("GET").path("/").authority("foo.com").scheme("https"); .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[] expectedPayload = headerPayload(streamId, headers);
byte[] expectedFrameBytes = { byte[] expectedFrameBytes = {
@ -170,7 +165,7 @@ public class DefaultHttp2FrameWriterTest {
http2HeadersEncoder.configuration().maxHeaderListSize(Integer.MAX_VALUE); http2HeadersEncoder.configuration().maxHeaderListSize(Integer.MAX_VALUE);
frameWriter.headersConfiguration().maxHeaderListSize(Integer.MAX_VALUE); frameWriter.headersConfiguration().maxHeaderListSize(Integer.MAX_VALUE);
frameWriter.maxFrameSize(Http2CodecUtil.MAX_FRAME_SIZE_LOWER_BOUND); 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); byte[] expectedPayload = headerPayload(streamId, headers);
@ -211,7 +206,7 @@ public class DefaultHttp2FrameWriterTest {
http2HeadersEncoder.configuration().maxHeaderListSize(Integer.MAX_VALUE); http2HeadersEncoder.configuration().maxHeaderListSize(Integer.MAX_VALUE);
frameWriter.headersConfiguration().maxHeaderListSize(Integer.MAX_VALUE); frameWriter.headersConfiguration().maxHeaderListSize(Integer.MAX_VALUE);
frameWriter.maxFrameSize(Http2CodecUtil.MAX_FRAME_SIZE_LOWER_BOUND); 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, byte[] expectedPayload = buildLargeHeaderPayload(streamId, headers, (byte) 4,
Http2CodecUtil.MAX_FRAME_SIZE_LOWER_BOUND); Http2CodecUtil.MAX_FRAME_SIZE_LOWER_BOUND);
@ -245,7 +240,7 @@ public class DefaultHttp2FrameWriterTest {
@Test @Test
public void writeFrameZeroPayload() throws Exception { 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[] expectedFrameBytes = {
(byte) 0x00, (byte) 0x00, (byte) 0x00, // payload length (byte) 0x00, (byte) 0x00, (byte) 0x00, // payload length
@ -264,7 +259,7 @@ public class DefaultHttp2FrameWriterTest {
// will auto release after frameWriter.writeFrame succeed // will auto release after frameWriter.writeFrame succeed
ByteBuf payloadByteBuf = Unpooled.wrappedBuffer(payload); 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[] expectedFrameHeaderBytes = {
(byte) 0x00, (byte) 0x00, (byte) 0x05, // payload length (byte) 0x00, (byte) 0x00, (byte) 0x05, // payload length
@ -279,7 +274,7 @@ public class DefaultHttp2FrameWriterTest {
@Test @Test
public void writePriority() { public void writePriority() {
frameWriter.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[] { expectedOutbound = Unpooled.copiedBuffer(new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x05, // payload length = 5 (byte) 0x00, (byte) 0x00, (byte) 0x05, // payload length = 5
@ -295,7 +290,7 @@ public class DefaultHttp2FrameWriterTest {
@Test @Test
public void writePriorityDefaults() { public void writePriorityDefaults() {
frameWriter.writePriority( 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[] { expectedOutbound = Unpooled.copiedBuffer(new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x05, // payload length = 5 (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) { 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) { 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") @SuppressWarnings("unchecked")
private void verifyWindowUpdateNotSent() { private void verifyWindowUpdateNotSent() {
verify(frameWriter, never()).writeWindowUpdate(any(ChannelHandlerContext.class), anyInt(), anyInt(), verify(frameWriter, never()).writeWindowUpdate(any(ChannelHandlerContext.class), anyInt(), anyInt());
any(Promise.class));
} }
private int window(int streamId) { 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.US_ASCII;
import static io.netty.util.CharsetUtil.UTF_8; import static io.netty.util.CharsetUtil.UTF_8;
import static java.util.concurrent.TimeUnit.SECONDS; 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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; 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.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
@ -157,7 +159,7 @@ public class Http2ConnectionHandlerTest {
buf.release(); buf.release();
return future; return future;
}).when(frameWriter).writeGoAway( }).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 -> { doAnswer((Answer<Future<Void>>) invocation -> {
Object o = invocation.getArguments()[0]; Object o = invocation.getArguments()[0];
if (o instanceof FutureListener) { if (o instanceof FutureListener) {
@ -167,6 +169,7 @@ public class Http2ConnectionHandlerTest {
}).when(future).addListener(any(FutureListener.class)); }).when(future).addListener(any(FutureListener.class));
when(future.cause()).thenReturn(fakeException); when(future.cause()).thenReturn(fakeException);
when(channel.isActive()).thenReturn(true); when(channel.isActive()).thenReturn(true);
when(future.isFailed()).thenReturn(true);
when(channel.pipeline()).thenReturn(pipeline); when(channel.pipeline()).thenReturn(pipeline);
when(connection.remote()).thenReturn(remote); when(connection.remote()).thenReturn(remote);
when(remote.flowController()).thenReturn(remoteFlowController); when(remote.flowController()).thenReturn(remoteFlowController);
@ -185,10 +188,13 @@ public class Http2ConnectionHandlerTest {
when(connection.goAwaySent(anyInt(), anyLong(), any(ByteBuf.class))).thenReturn(true); when(connection.goAwaySent(anyInt(), anyLong(), any(ByteBuf.class))).thenReturn(true);
when(stream.open(anyBoolean())).thenReturn(stream); when(stream.open(anyBoolean())).thenReturn(stream);
when(encoder.writeSettings(any(ChannelHandlerContext.class), 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.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT);
when(ctx.channel()).thenReturn(channel); 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.newPromise()).thenReturn(promise);
when(ctx.write(any())).thenReturn(future); when(ctx.write(any())).thenReturn(future);
when(ctx.executor()).thenReturn(executor); when(ctx.executor()).thenReturn(executor);
@ -254,7 +260,7 @@ public class Http2ConnectionHandlerTest {
final Answer<Object> verifier = in -> { final Answer<Object> verifier = in -> {
assertEquals(in.getArgument(0), evt); // sanity check... assertEquals(in.getArgument(0), evt); // sanity check...
verify(ctx).write(eq(connectionPrefaceBuf())); 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); verified.set(true);
return null; return null;
}; };
@ -292,7 +298,7 @@ public class Http2ConnectionHandlerTest {
handler.channelRead(ctx, copiedBuffer("BAD_PREFACE", UTF_8)); handler.channelRead(ctx, copiedBuffer("BAD_PREFACE", UTF_8));
ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class); ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);
verify(frameWriter).writeGoAway(any(ChannelHandlerContext.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()); assertEquals(0, captor.getValue().refCnt());
} }
@ -303,7 +309,7 @@ public class Http2ConnectionHandlerTest {
handler.channelRead(ctx, copiedBuffer("GET /path HTTP/1.1", US_ASCII)); handler.channelRead(ctx, copiedBuffer("GET /path HTTP/1.1", US_ASCII));
ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class); ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);
verify(frameWriter).writeGoAway(any(ChannelHandlerContext.class), eq(Integer.MAX_VALUE), 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()); assertEquals(0, captor.getValue().refCnt());
assertTrue(goAwayDebugCap.contains("/path")); assertTrue(goAwayDebugCap.contains("/path"));
} }
@ -319,7 +325,7 @@ public class Http2ConnectionHandlerTest {
handler.channelRead(ctx, buf); handler.channelRead(ctx, buf);
ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class); ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);
verify(frameWriter, atLeastOnce()).writeGoAway(any(ChannelHandlerContext.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()); assertEquals(0, captor.getValue().refCnt());
} }
@ -373,7 +379,7 @@ public class Http2ConnectionHandlerTest {
handler.exceptionCaught(ctx, e); handler.exceptionCaught(ctx, e);
ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class); ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);
verify(frameWriter).writeGoAway(eq(ctx), eq(Integer.MAX_VALUE), eq(PROTOCOL_ERROR.code()), verify(frameWriter).writeGoAway(eq(ctx), eq(Integer.MAX_VALUE), eq(PROTOCOL_ERROR.code()),
captor.capture(), eq(promise)); captor.capture());
captor.getValue().release(); captor.getValue().release();
} }
@ -389,16 +395,16 @@ public class Http2ConnectionHandlerTest {
when(stream.isHeadersSent()).thenReturn(false); when(stream.isHeadersSent()).thenReturn(false);
when(remote.lastStreamCreated()).thenReturn(STREAM_ID); when(remote.lastStreamCreated()).thenReturn(STREAM_ID);
when(frameWriter.writeRstStream(eq(ctx), eq(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); handler.exceptionCaught(ctx, e);
ArgumentCaptor<Http2Headers> captor = ArgumentCaptor.forClass(Http2Headers.class); ArgumentCaptor<Http2Headers> captor = ArgumentCaptor.forClass(Http2Headers.class);
verify(encoder).writeHeaders(eq(ctx), eq(STREAM_ID), 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(); Http2Headers headers = captor.getValue();
assertEquals(HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE.codeAsText(), headers.status()); 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 @Test
@ -413,13 +419,13 @@ public class Http2ConnectionHandlerTest {
when(stream.isHeadersSent()).thenReturn(false); when(stream.isHeadersSent()).thenReturn(false);
when(remote.lastStreamCreated()).thenReturn(STREAM_ID); when(remote.lastStreamCreated()).thenReturn(STREAM_ID);
when(frameWriter.writeRstStream(eq(ctx), eq(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); handler.exceptionCaught(ctx, e);
verify(encoder, never()).writeHeaders(eq(ctx), eq(STREAM_ID), 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 @Test
@ -434,13 +440,13 @@ public class Http2ConnectionHandlerTest {
when(stream.isHeadersSent()).thenReturn(false); when(stream.isHeadersSent()).thenReturn(false);
when(remote.lastStreamCreated()).thenReturn(STREAM_ID); when(remote.lastStreamCreated()).thenReturn(STREAM_ID);
when(frameWriter.writeRstStream(eq(ctx), eq(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); handler.exceptionCaught(ctx, e);
verify(encoder, never()).writeHeaders(eq(ctx), eq(STREAM_ID), 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 @Test
@ -448,7 +454,7 @@ public class Http2ConnectionHandlerTest {
final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1);
handler = new Http2ConnectionHandler(decoder, encoder, new Http2Settings()) { handler = new Http2ConnectionHandler(decoder, encoder, new Http2Settings()) {
@Override @Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt == Http2ConnectionPrefaceAndSettingsFrameWrittenEvent.INSTANCE) { if (evt == Http2ConnectionPrefaceAndSettingsFrameWrittenEvent.INSTANCE) {
latch.countDown(); latch.countDown();
} }
@ -470,13 +476,13 @@ public class Http2ConnectionHandlerTest {
when(stream.isHeadersSent()).thenReturn(true); when(stream.isHeadersSent()).thenReturn(true);
when(remote.lastStreamCreated()).thenReturn(STREAM_ID); when(remote.lastStreamCreated()).thenReturn(STREAM_ID);
when(frameWriter.writeRstStream(eq(ctx), eq(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); handler.exceptionCaught(ctx, e);
verify(encoder, never()).writeHeaders(eq(ctx), eq(STREAM_ID), 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 @Test
@ -494,14 +500,14 @@ public class Http2ConnectionHandlerTest {
when(stream.isHeadersSent()).thenReturn(false); when(stream.isHeadersSent()).thenReturn(false);
when(remote.lastStreamCreated()).thenReturn(STREAM_ID); when(remote.lastStreamCreated()).thenReturn(STREAM_ID);
when(frameWriter.writeRstStream(eq(ctx), eq(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); handler.exceptionCaught(ctx, e);
verify(remote).createStream(STREAM_ID, true); verify(remote).createStream(STREAM_ID, true);
verify(encoder).writeHeaders(eq(ctx), eq(STREAM_ID), 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 @Test
@ -518,9 +524,9 @@ public class Http2ConnectionHandlerTest {
public void writeRstOnNonExistantStreamShouldSucceed() throws Exception { public void writeRstOnNonExistantStreamShouldSucceed() throws Exception {
handler = newHandler(); handler = newHandler();
when(frameWriter.writeRstStream(eq(ctx), eq(NON_EXISTANT_STREAM_ID), when(frameWriter.writeRstStream(eq(ctx), eq(NON_EXISTANT_STREAM_ID),
eq(STREAM_CLOSED.code()), eq(promise))).thenReturn(future); eq(STREAM_CLOSED.code()))).thenReturn(future);
handler.resetStream(ctx, NON_EXISTANT_STREAM_ID, STREAM_CLOSED.code(), promise); handler.resetStream(ctx, NON_EXISTANT_STREAM_ID, STREAM_CLOSED.code());
verify(frameWriter).writeRstStream(eq(ctx), eq(NON_EXISTANT_STREAM_ID), eq(STREAM_CLOSED.code()), eq(promise)); verify(frameWriter).writeRstStream(eq(ctx), eq(NON_EXISTANT_STREAM_ID), eq(STREAM_CLOSED.code()));
} }
@Test @Test
@ -528,21 +534,21 @@ public class Http2ConnectionHandlerTest {
handler = newHandler(); handler = newHandler();
when(stream.id()).thenReturn(STREAM_ID); when(stream.id()).thenReturn(STREAM_ID);
when(frameWriter.writeRstStream(eq(ctx), eq(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.state()).thenReturn(CLOSED);
when(stream.isHeadersSent()).thenReturn(true); when(stream.isHeadersSent()).thenReturn(true);
// The stream is "closed" but is still known about by the connection (connection().stream(..) // 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. // will return the stream). We should still write a RST_STREAM frame in this scenario.
handler.resetStream(ctx, STREAM_ID, STREAM_CLOSED.code(), promise); handler.resetStream(ctx, STREAM_ID, STREAM_CLOSED.code());
verify(frameWriter).writeRstStream(eq(ctx), eq(STREAM_ID), anyLong(), any(Promise.class)); verify(frameWriter).writeRstStream(eq(ctx), eq(STREAM_ID), anyLong());
} }
@Test @Test
public void writeRstOnIdleStreamShouldNotWriteButStillSucceed() throws Exception { public void writeRstOnIdleStreamShouldNotWriteButStillSucceed() throws Exception {
handler = newHandler(); handler = newHandler();
when(stream.state()).thenReturn(IDLE); when(stream.state()).thenReturn(IDLE);
handler.resetStream(ctx, STREAM_ID, STREAM_CLOSED.code(), promise); handler.resetStream(ctx, STREAM_ID, STREAM_CLOSED.code());
verify(frameWriter, never()).writeRstStream(eq(ctx), eq(STREAM_ID), anyLong(), any(Promise.class)); verify(frameWriter, never()).writeRstStream(eq(ctx), eq(STREAM_ID), anyLong());
verify(stream).close(); verify(stream).close();
} }
@ -585,11 +591,10 @@ public class Http2ConnectionHandlerTest {
return null; return null;
}).when(future).addListener(any(FutureListener.class)); }).when(future).addListener(any(FutureListener.class));
handler = newHandler(); 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(connection).goAwaySent(eq(STREAM_ID), eq(errorCode), eq(data));
verify(frameWriter).writeGoAway(eq(ctx), eq(STREAM_ID), eq(errorCode), eq(data), verify(frameWriter).writeGoAway(eq(ctx), eq(STREAM_ID), eq(errorCode), eq(data));
eq(promise));
verify(ctx).close(); verify(ctx).close();
assertEquals(0, data.refCnt()); assertEquals(0, data.refCnt());
} }
@ -600,13 +605,12 @@ public class Http2ConnectionHandlerTest {
ByteBuf data = dummyData(); ByteBuf data = dummyData();
long errorCode = Http2Error.INTERNAL_ERROR.code(); long errorCode = Http2Error.INTERNAL_ERROR.code();
handler.goAway(ctx, STREAM_ID + 2, errorCode, data.retain(), promise); handler.goAway(ctx, STREAM_ID + 2, errorCode, data.retain());
verify(frameWriter).writeGoAway(eq(ctx), eq(STREAM_ID + 2), eq(errorCode), eq(data), verify(frameWriter).writeGoAway(eq(ctx), eq(STREAM_ID + 2), eq(errorCode), eq(data));
eq(promise));
verify(connection).goAwaySent(eq(STREAM_ID + 2), eq(errorCode), eq(data)); verify(connection).goAwaySent(eq(STREAM_ID + 2), eq(errorCode), eq(data));
promise = new DefaultPromise<>(ImmediateEventExecutor.INSTANCE); promise = new DefaultPromise<>(ImmediateEventExecutor.INSTANCE);
handler.goAway(ctx, STREAM_ID, errorCode, data, promise); handler.goAway(ctx, STREAM_ID, errorCode, 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(connection).goAwaySent(eq(STREAM_ID), eq(errorCode), eq(data)); verify(connection).goAwaySent(eq(STREAM_ID), eq(errorCode), eq(data));
assertEquals(0, data.refCnt()); assertEquals(0, data.refCnt());
} }
@ -617,20 +621,24 @@ public class Http2ConnectionHandlerTest {
ByteBuf data = dummyData(); ByteBuf data = dummyData();
long errorCode = Http2Error.INTERNAL_ERROR.code(); 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(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. // 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(connection.goAwaySent()).thenReturn(true);
when(remote.lastStreamKnownByPeer()).thenReturn(STREAM_ID); when(remote.lastStreamKnownByPeer()).thenReturn(STREAM_ID);
Exception ex = new IllegalStateException();
doAnswer((Answer<Boolean>) invocationOnMock -> { doAnswer((Answer<Boolean>) invocationOnMock -> {
throw new IllegalStateException(); throw ex;
}).when(connection).goAwaySent(anyInt(), anyLong(), any(ByteBuf.class)); }).when(connection).goAwaySent(anyInt(), anyLong(), any(ByteBuf.class));
handler.goAway(ctx, STREAM_ID + 2, errorCode, data, promise); Future<Void> future2 = handler.goAway(ctx, STREAM_ID + 2, errorCode, data);
assertTrue(promise.isDone()); assertTrue(future2.isDone());
assertFalse(promise.isSuccess()); assertFalse(future2.isSuccess());
assertSame(ex, future2.cause());
assertEquals(0, data.refCnt()); assertEquals(0, data.refCnt());
verifyNoMoreInteractions(frameWriter); verifyNoMoreInteractions(frameWriter);
} }
@ -657,9 +665,8 @@ public class Http2ConnectionHandlerTest {
when(channel.isActive()).thenReturn(false); when(channel.isActive()).thenReturn(false);
handler.channelInactive(ctx); handler.channelInactive(ctx);
verify(frameWriter, never()).writeGoAway(any(ChannelHandlerContext.class), anyInt(), anyLong(), verify(frameWriter, never()).writeGoAway(any(ChannelHandlerContext.class), anyInt(), anyLong(),
any(ByteBuf.class), any(Promise.class)); any(ByteBuf.class));
verify(frameWriter, never()).writeRstStream(any(ChannelHandlerContext.class), anyInt(), anyLong(), verify(frameWriter, never()).writeRstStream(any(ChannelHandlerContext.class), anyInt(), anyLong());
any(Promise.class));
} }
@Test @Test
@ -729,22 +736,14 @@ public class Http2ConnectionHandlerTest {
return stream; return stream;
}); });
when(stream.isResetSent()).then((Answer<Boolean>) invocationOnMock -> resetSent.get()); when(stream.isResetSent()).then((Answer<Boolean>) invocationOnMock -> resetSent.get());
when(frameWriter.writeRstStream(eq(ctx), eq(STREAM_ID), anyLong(), any(Promise.class))) when(frameWriter.writeRstStream(eq(ctx), eq(STREAM_ID), anyLong()))
.then((Answer<Future<Void>>) invocationOnMock -> { .thenReturn(ImmediateEventExecutor.INSTANCE.newSucceededFuture(null));
Promise<Void> promise = invocationOnMock.getArgument(3);
return promise.setSuccess(null);
});
Promise<Void> promise = Future<Void> f1 = handler.resetStream(ctx, STREAM_ID, STREAM_CLOSED.code());
new DefaultPromise<>(ImmediateEventExecutor.INSTANCE); Future<Void> f2 = handler.resetStream(ctx, STREAM_ID, CANCEL.code());
final Promise<Void> promise2 = verify(frameWriter).writeRstStream(eq(ctx), eq(STREAM_ID), anyLong());
new DefaultPromise<>(ImmediateEventExecutor.INSTANCE); assertTrue(f1.isSuccess());
promise.addListener(future -> handler.resetStream(ctx, STREAM_ID, STREAM_CLOSED.code(), promise2)); assertTrue(f2.isSuccess());
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());
} }
private static ByteBuf dummyData() { private static ByteBuf dummyData() {

View File

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

View File

@ -33,7 +33,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@ -59,7 +58,6 @@ import static org.mockito.Mockito.when;
/** /**
* Tests for {@link Http2ControlFrameLimitEncoder}. * Tests for {@link Http2ControlFrameLimitEncoder}.
*/ */
@SuppressWarnings("unchecked")
public class Http2ControlFrameLimitEncoderTest { public class Http2ControlFrameLimitEncoderTest {
private Http2ControlFrameLimitEncoder encoder; private Http2ControlFrameLimitEncoder encoder;
@ -101,22 +99,22 @@ public class Http2ControlFrameLimitEncoderTest {
when(configuration.frameSizePolicy()).thenReturn(frameSizePolicy); when(configuration.frameSizePolicy()).thenReturn(frameSizePolicy);
when(frameSizePolicy.maxFrameSize()).thenReturn(DEFAULT_MAX_FRAME_SIZE); when(frameSizePolicy.maxFrameSize()).thenReturn(DEFAULT_MAX_FRAME_SIZE);
when(writer.writeRstStream(eq(ctx), anyInt(), anyLong(), any(Promise.class))) when(writer.writeRstStream(eq(ctx), anyInt(), anyLong()))
.thenAnswer((Answer<Future<Void>>) invocationOnMock -> handlePromise(invocationOnMock, 3)); .thenAnswer((Answer<Future<Void>>) invocationOnMock -> handlePromise());
when(writer.writeSettingsAck(any(ChannelHandlerContext.class), any(Promise.class))) when(writer.writeSettingsAck(any(ChannelHandlerContext.class)))
.thenAnswer((Answer<Future<Void>>) invocationOnMock -> handlePromise(invocationOnMock, 1)); .thenAnswer((Answer<Future<Void>>) invocationOnMock -> handlePromise());
when(writer.writePing(any(ChannelHandlerContext.class), anyBoolean(), anyLong(), any(Promise.class))) when(writer.writePing(any(ChannelHandlerContext.class), anyBoolean(), anyLong()))
.thenAnswer((Answer<Future<Void>>) invocationOnMock -> { .thenAnswer((Answer<Future<Void>>) invocationOnMock -> {
Promise<Void> promise = handlePromise(invocationOnMock, 3); Promise<Void> promise = handlePromise();
if (invocationOnMock.getArgument(1) == Boolean.FALSE) { if (invocationOnMock.getArgument(1) == Boolean.FALSE) {
promise.trySuccess(null); promise.trySuccess(null);
} }
return promise; return promise;
}); });
when(writer.writeGoAway(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class), when(writer.writeGoAway(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)))
any(Promise.class))).thenAnswer((Answer<Future<Void>>) invocationOnMock -> { .thenAnswer((Answer<Future<Void>>) invocationOnMock -> {
ReferenceCountUtil.release(invocationOnMock.getArgument(3)); ReferenceCountUtil.release(invocationOnMock.getArgument(3));
Promise<Void> promise = invocationOnMock.getArgument(4); Promise<Void> promise = ImmediateEventExecutor.INSTANCE.newPromise();
goAwayPromises.offer(promise); goAwayPromises.offer(promise);
return promise; return promise;
}); });
@ -139,7 +137,12 @@ public class Http2ControlFrameLimitEncoderTest {
when(channel.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT); when(channel.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT);
when(executor.inEventLoop()).thenReturn(true); when(executor.inEventLoop()).thenReturn(true);
doAnswer((Answer<Promise>) invocation -> newPromise()).when(ctx).newPromise(); 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.executor()).thenReturn(executor);
when(ctx.close()).thenReturn(ImmediateEventExecutor.INSTANCE.newSucceededFuture(null));
when(channel.isActive()).thenReturn(false); when(channel.isActive()).thenReturn(false);
when(channel.config()).thenReturn(config); when(channel.config()).thenReturn(config);
when(channel.isWritable()).thenReturn(true); when(channel.isWritable()).thenReturn(true);
@ -152,12 +155,12 @@ public class Http2ControlFrameLimitEncoderTest {
handler.handlerAdded(ctx); handler.handlerAdded(ctx);
} }
private Promise<Void> handlePromise(InvocationOnMock invocationOnMock, int promiseIdx) { private Promise<Void> handlePromise() {
Promise<Void> promise = invocationOnMock.getArgument(promiseIdx); Promise<Void> p = ImmediateEventExecutor.INSTANCE.newPromise();
if (++numWrites == 2) { if (++numWrites == 2) {
promise.setSuccess(null); p.setSuccess(null);
} }
return promise; return p;
} }
@AfterEach @AfterEach
@ -178,75 +181,75 @@ public class Http2ControlFrameLimitEncoderTest {
@Test @Test
public void testLimitSettingsAck() { 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 // 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. // not count to the number of queued frames.
assertTrue(encoder.writeSettingsAck(ctx, newPromise()).isSuccess()); assertTrue(encoder.writeSettingsAck(ctx).isSuccess());
assertFalse(encoder.writeSettingsAck(ctx, newPromise()).isDone()); assertFalse(encoder.writeSettingsAck(ctx).isDone());
verifyFlushAndClose(0, false); verifyFlushAndClose(0, false);
assertFalse(encoder.writeSettingsAck(ctx, newPromise()).isDone()); assertFalse(encoder.writeSettingsAck(ctx).isDone());
assertFalse(encoder.writeSettingsAck(ctx, newPromise()).isDone()); assertFalse(encoder.writeSettingsAck(ctx).isDone());
verifyFlushAndClose(1, true); verifyFlushAndClose(1, true);
} }
@Test @Test
public void testLimitPingAck() { 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 // 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. // not count to the number of queued frames.
assertTrue(encoder.writePing(ctx, true, 8, newPromise()).isSuccess()); assertTrue(encoder.writePing(ctx, true, 8).isSuccess());
assertFalse(encoder.writePing(ctx, true, 8, newPromise()).isDone()); assertFalse(encoder.writePing(ctx, true, 8).isDone());
verifyFlushAndClose(0, false); verifyFlushAndClose(0, false);
assertFalse(encoder.writePing(ctx, true, 8, newPromise()).isDone()); assertFalse(encoder.writePing(ctx, true, 8).isDone());
assertFalse(encoder.writePing(ctx, true, 8, newPromise()).isDone()); assertFalse(encoder.writePing(ctx, true, 8).isDone());
verifyFlushAndClose(1, true); verifyFlushAndClose(1, true);
} }
@Test @Test
public void testNotLimitPing() { public void testNotLimitPing() {
assertTrue(encoder.writePing(ctx, false, 8, newPromise()).isSuccess()); assertTrue(encoder.writePing(ctx, false, 8).isSuccess());
assertTrue(encoder.writePing(ctx, false, 8, newPromise()).isSuccess()); assertTrue(encoder.writePing(ctx, false, 8).isSuccess());
assertTrue(encoder.writePing(ctx, false, 8, newPromise()).isSuccess()); assertTrue(encoder.writePing(ctx, false, 8).isSuccess());
assertTrue(encoder.writePing(ctx, false, 8, newPromise()).isSuccess()); assertTrue(encoder.writePing(ctx, false, 8).isSuccess());
verifyFlushAndClose(0, false); verifyFlushAndClose(0, false);
} }
@Test @Test
public void testLimitRst() { 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 // 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. // not count to the number of queued frames.
assertTrue(encoder.writeRstStream(ctx, 1, CANCEL.code(), newPromise()).isSuccess()); assertTrue(encoder.writeRstStream(ctx, 1, CANCEL.code()).isSuccess());
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code(), newPromise()).isDone()); assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code()).isDone());
verifyFlushAndClose(0, false); verifyFlushAndClose(0, false);
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code(), newPromise()).isDone()); assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code()).isDone());
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code(), newPromise()).isDone()); assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code()).isDone());
verifyFlushAndClose(1, true); verifyFlushAndClose(1, true);
} }
@Test @Test
public void testLimit() { 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 // 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. // not count to the number of queued frames.
assertTrue(encoder.writePing(ctx, false, 8, newPromise()).isSuccess()); assertTrue(encoder.writePing(ctx, false, 8).isSuccess());
assertFalse(encoder.writePing(ctx, true, 8, newPromise()).isSuccess()); assertFalse(encoder.writePing(ctx, true, 8).isSuccess());
verifyFlushAndClose(0, false); verifyFlushAndClose(0, false);
assertFalse(encoder.writeSettingsAck(ctx, newPromise()).isDone()); assertFalse(encoder.writeSettingsAck(ctx).isDone());
assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code(), newPromise()).isDone()); assertFalse(encoder.writeRstStream(ctx, 1, CANCEL.code()).isDone());
assertFalse(encoder.writePing(ctx, true, 8, newPromise()).isSuccess()); assertFalse(encoder.writePing(ctx, true, 8).isSuccess());
verifyFlushAndClose(1, true); verifyFlushAndClose(1, true);
} }
@ -256,7 +259,7 @@ public class Http2ControlFrameLimitEncoderTest {
verify(ctx, times(invocations)).close(); verify(ctx, times(invocations)).close();
if (failed) { if (failed) {
verify(writer, times(1)).writeGoAway(eq(ctx), eq(Integer.MAX_VALUE), eq(ENHANCE_YOUR_CALM.code()), 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.Http2CodecUtil.isStreamIdValid;
import static io.netty.handler.codec.http2.Http2Error.NO_ERROR; 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.anyHttp2Settings;
import static io.netty.handler.codec.http2.Http2TestUtil.assertEqualsAndRelease; import static io.netty.handler.codec.http2.Http2TestUtil.assertEqualsAndRelease;
import static io.netty.handler.codec.http2.Http2TestUtil.bb; import static io.netty.handler.codec.http2.Http2TestUtil.bb;
@ -82,7 +81,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
/** /**
* Unit tests for {@link Http2FrameCodec}. * Unit tests for {@link Http2FrameCodec}.
*/ */
@SuppressWarnings("unchecked")
public class Http2FrameCodecTest { public class Http2FrameCodecTest {
// For verifying outbound frames // For verifying outbound frames
@ -141,13 +139,13 @@ public class Http2FrameCodecTest {
channel.pipeline().fireChannelActive(); channel.pipeline().fireChannelActive();
// Handshake // Handshake
verify(frameWriter).writeSettings(any(ChannelHandlerContext.class), anyHttp2Settings(), anyChannelPromise()); verify(frameWriter).writeSettings(any(ChannelHandlerContext.class), anyHttp2Settings());
verifyNoMoreInteractions(frameWriter); verifyNoMoreInteractions(frameWriter);
channel.writeInbound(Http2CodecUtil.connectionPrefaceBuf()); channel.writeInbound(Http2CodecUtil.connectionPrefaceBuf());
frameInboundWriter.writeInboundSettings(initialRemoteSettings); frameInboundWriter.writeInboundSettings(initialRemoteSettings);
verify(frameWriter).writeSettingsAck(any(ChannelHandlerContext.class), anyChannelPromise()); verify(frameWriter).writeSettingsAck(any(ChannelHandlerContext.class));
frameInboundWriter.writeInboundSettingsAck(); frameInboundWriter.writeInboundSettingsAck();
@ -178,9 +176,9 @@ public class Http2FrameCodecTest {
channel.writeOutbound(new DefaultHttp2HeadersFrame(response, true, 27).stream(stream2)); channel.writeOutbound(new DefaultHttp2HeadersFrame(response, true, 27).stream(stream2));
verify(frameWriter).writeHeaders( verify(frameWriter).writeHeaders(
any(ChannelHandlerContext.class), eq(1), eq(response), any(ChannelHandlerContext.class), eq(1), eq(response),
eq(27), eq(true), anyChannelPromise()); eq(27), eq(true));
verify(frameWriter, never()).writeRstStream( verify(frameWriter, never()).writeRstStream(
any(ChannelHandlerContext.class), anyInt(), anyLong(), anyChannelPromise()); any(ChannelHandlerContext.class), anyInt(), anyLong());
assertEquals(State.CLOSED, stream.state()); assertEquals(State.CLOSED, stream.state());
event = inboundHandler.readInboundMessageOrUserEvent(); event = inboundHandler.readInboundMessageOrUserEvent();
@ -207,9 +205,9 @@ public class Http2FrameCodecTest {
channel.writeOutbound(new DefaultHttp2HeadersFrame(response, true, 27).stream(stream2)); channel.writeOutbound(new DefaultHttp2HeadersFrame(response, true, 27).stream(stream2));
verify(frameWriter).writeHeaders( verify(frameWriter).writeHeaders(
any(ChannelHandlerContext.class), eq(1), eq(response), any(ChannelHandlerContext.class), eq(1), eq(response),
eq(27), eq(true), anyChannelPromise()); eq(27), eq(true));
verify(frameWriter, never()).writeRstStream( verify(frameWriter, never()).writeRstStream(
any(ChannelHandlerContext.class), anyInt(), anyLong(), anyChannelPromise()); any(ChannelHandlerContext.class), anyInt(), anyLong());
assertEquals(State.CLOSED, stream.state()); assertEquals(State.CLOSED, stream.state());
assertTrue(channel.isActive()); assertTrue(channel.isActive());
@ -266,12 +264,12 @@ public class Http2FrameCodecTest {
inboundHandler.writeOutbound(new DefaultHttp2HeadersFrame(response, false).stream(stream2)); inboundHandler.writeOutbound(new DefaultHttp2HeadersFrame(response, false).stream(stream2));
verify(frameWriter).writeHeaders(any(ChannelHandlerContext.class), eq(1), eq(response), eq(0), 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)); channel.writeOutbound(new DefaultHttp2DataFrame(bb("world"), true, 27).stream(stream2));
ArgumentCaptor<ByteBuf> outboundData = ArgumentCaptor.forClass(ByteBuf.class); ArgumentCaptor<ByteBuf> outboundData = ArgumentCaptor.forClass(ByteBuf.class);
verify(frameWriter).writeData(any(ChannelHandlerContext.class), eq(1), outboundData.capture(), eq(27), verify(frameWriter).writeData(any(ChannelHandlerContext.class), eq(1), outboundData.capture(), eq(27),
eq(true), anyChannelPromise()); eq(true));
ByteBuf bb = bb("world"); ByteBuf bb = bb("world");
assertEquals(bb, outboundData.getValue()); assertEquals(bb, outboundData.getValue());
@ -280,7 +278,7 @@ public class Http2FrameCodecTest {
outboundData.getValue().release(); outboundData.getValue().release();
verify(frameWriter, never()).writeRstStream(any(ChannelHandlerContext.class), verify(frameWriter, never()).writeRstStream(any(ChannelHandlerContext.class),
anyInt(), anyLong(), anyChannelPromise()); anyInt(), anyLong());
assertTrue(channel.isActive()); assertTrue(channel.isActive());
} }
@ -301,7 +299,7 @@ public class Http2FrameCodecTest {
assertEquals(3, stream2.id()); assertEquals(3, stream2.id());
channel.writeOutbound(new DefaultHttp2ResetFrame(314 /* non-standard error */).stream(stream2)); 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()); assertEquals(State.CLOSED, stream.state());
assertTrue(channel.isActive()); assertTrue(channel.isActive());
} }
@ -343,7 +341,7 @@ public class Http2FrameCodecTest {
channel.writeOutbound(goAwayFrame); channel.writeOutbound(goAwayFrame);
verify(frameWriter).writeGoAway(any(ChannelHandlerContext.class), eq(7), 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()); assertEquals(State.OPEN, stream.state());
assertTrue(channel.isActive()); assertTrue(channel.isActive());
expected.release(); expected.release();
@ -419,7 +417,7 @@ public class Http2FrameCodecTest {
channel.writeOutbound(goAwayFrame); channel.writeOutbound(goAwayFrame);
// When the last stream id computation overflows, the last stream id should just be set to 2^31 - 1. // 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), 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(); debugData.release();
assertEquals(State.OPEN, stream.state()); assertEquals(State.OPEN, stream.state());
assertTrue(channel.isActive()); assertTrue(channel.isActive());
@ -596,7 +594,7 @@ public class Http2FrameCodecTest {
channel.write(unknownFrame); channel.write(unknownFrame);
verify(frameWriter).writeFrame(any(ChannelHandlerContext.class), eq(unknownFrame.frameType()), 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 @Test
@ -604,7 +602,7 @@ public class Http2FrameCodecTest {
Http2Settings settings = new Http2Settings(); Http2Settings settings = new Http2Settings();
channel.write(new DefaultHttp2SettingsFrame(settings)); 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 @Test
@ -763,7 +761,7 @@ public class Http2FrameCodecTest {
channel.writeAndFlush(new DefaultHttp2PingFrame(12345)); channel.writeAndFlush(new DefaultHttp2PingFrame(12345));
verify(frameWriter).writePing(any(ChannelHandlerContext.class), eq(false), verify(frameWriter).writePing(any(ChannelHandlerContext.class), eq(false),
eq(12345L), anyChannelPromise()); eq(12345L));
} }
@Test @Test
@ -781,7 +779,7 @@ public class Http2FrameCodecTest {
Http2Settings settings = new Http2Settings().maxConcurrentStreams(1); Http2Settings settings = new Http2Settings().maxConcurrentStreams(1);
channel.writeAndFlush(new DefaultHttp2SettingsFrame(settings)); channel.writeAndFlush(new DefaultHttp2SettingsFrame(settings));
verify(frameWriter).writeSettings(any(ChannelHandlerContext.class), eq(settings), anyChannelPromise()); verify(frameWriter).writeSettings(any(ChannelHandlerContext.class), eq(settings));
} }
@Test @Test
@ -837,21 +835,21 @@ public class Http2FrameCodecTest {
Http2PingFrame frame = inboundHandler.readInbound(); Http2PingFrame frame = inboundHandler.readInbound();
assertFalse(frame.ack()); assertFalse(frame.ack());
assertEquals(8, frame.content()); 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 @Test
public void autoAckPingFalse() throws Exception { public void autoAckPingFalse() throws Exception {
setUp(Http2FrameCodecBuilder.forServer().autoAckPingFrame(false), new Http2Settings()); setUp(Http2FrameCodecBuilder.forServer().autoAckPingFrame(false), new Http2Settings());
frameInboundWriter.writeInboundPing(false, 8); 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(); Http2PingFrame frame = inboundHandler.readInbound();
assertFalse(frame.ack()); assertFalse(frame.ack());
assertEquals(8, frame.content()); assertEquals(8, frame.content());
// Now ack the frame manually. // Now ack the frame manually.
channel.writeAndFlush(new DefaultHttp2PingFrame(8, true)); 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 @Test

View File

@ -49,59 +49,59 @@ final class Http2FrameInboundWriter {
} }
void writeInboundData(int streamId, ByteBuf data, int padding, boolean endStream) { 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, void writeInboundHeaders(int streamId, Http2Headers headers,
int padding, boolean endStream) { 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, void writeInboundHeaders(int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) { int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) {
writer.writeHeaders(ctx, streamId, headers, streamDependency, writer.writeHeaders(ctx, streamId, headers, streamDependency,
weight, exclusive, padding, endStream, ctx.newPromise()).syncUninterruptibly(); weight, exclusive, padding, endStream).syncUninterruptibly();
} }
void writeInboundPriority(int streamId, int streamDependency, void writeInboundPriority(int streamId, int streamDependency,
short weight, boolean exclusive) { short weight, boolean exclusive) {
writer.writePriority(ctx, streamId, streamDependency, weight, writer.writePriority(ctx, streamId, streamDependency, weight,
exclusive, ctx.newPromise()).syncUninterruptibly(); exclusive).syncUninterruptibly();
} }
void writeInboundRstStream(int streamId, long errorCode) { void writeInboundRstStream(int streamId, long errorCode) {
writer.writeRstStream(ctx, streamId, errorCode, ctx.newPromise()).syncUninterruptibly(); writer.writeRstStream(ctx, streamId, errorCode).syncUninterruptibly();
} }
void writeInboundSettings(Http2Settings settings) { void writeInboundSettings(Http2Settings settings) {
writer.writeSettings(ctx, settings, ctx.newPromise()).syncUninterruptibly(); writer.writeSettings(ctx, settings).syncUninterruptibly();
} }
void writeInboundSettingsAck() { void writeInboundSettingsAck() {
writer.writeSettingsAck(ctx, ctx.newPromise()).syncUninterruptibly(); writer.writeSettingsAck(ctx).syncUninterruptibly();
} }
void writeInboundPing(boolean ack, long data) { 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, void writePushPromise(int streamId, int promisedStreamId,
Http2Headers headers, int padding) { Http2Headers headers, int padding) {
writer.writePushPromise(ctx, streamId, promisedStreamId, writer.writePushPromise(ctx, streamId, promisedStreamId,
headers, padding, ctx.newPromise()).syncUninterruptibly(); headers, padding).syncUninterruptibly();
} }
void writeInboundGoAway(int lastStreamId, long errorCode, ByteBuf debugData) { 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) { 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, void writeInboundFrame(byte frameType, int streamId,
Http2Flags flags, ByteBuf payload) { 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 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.atLeastOnce;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq; import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isA;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -134,7 +133,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void emptyDataShouldMatch() throws Exception { public void emptyDataShouldMatch() throws Exception {
final ByteBuf data = EMPTY_BUFFER; 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(); readFrames();
verify(listener).onDataRead(eq(ctx), eq(STREAM_ID), eq(data), eq(0), eq(false)); verify(listener).onDataRead(eq(ctx), eq(STREAM_ID), eq(data), eq(0), eq(false));
} }
@ -142,7 +141,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void dataShouldMatch() throws Exception { public void dataShouldMatch() throws Exception {
final ByteBuf data = data(10); 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(); readFrames();
verify(listener).onDataRead(eq(ctx), eq(STREAM_ID), eq(data), eq(1), eq(false)); verify(listener).onDataRead(eq(ctx), eq(STREAM_ID), eq(data), eq(1), eq(false));
} }
@ -150,7 +149,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void dataWithPaddingShouldMatch() throws Exception { public void dataWithPaddingShouldMatch() throws Exception {
final ByteBuf data = data(10); 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(); readFrames();
verify(listener).onDataRead(eq(ctx), eq(STREAM_ID), eq(data), eq(MAX_PADDING), eq(true)); 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; final boolean endOfStream = true;
writer.writeData(ctx, STREAM_ID, originalData.slice(), originalPadding, writer.writeData(ctx, STREAM_ID, originalData.slice(), originalPadding,
endOfStream, ctx.newPromise()); endOfStream);
readFrames(); readFrames();
// Verify that at least one frame was sent with eos=false and exactly one with eos=true. // 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 @Test
public void emptyHeadersShouldMatch() throws Exception { public void emptyHeadersShouldMatch() throws Exception {
final Http2Headers headers = EmptyHttp2Headers.INSTANCE; final Http2Headers headers = EmptyHttp2Headers.INSTANCE;
writer.writeHeaders(ctx, STREAM_ID, headers, 0, true, ctx.newPromise()); writer.writeHeaders(ctx, STREAM_ID, headers, 0, true);
readFrames(); readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(0), eq(true)); verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(0), eq(true));
} }
@ -204,7 +203,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void emptyHeadersWithPaddingShouldMatch() throws Exception { public void emptyHeadersWithPaddingShouldMatch() throws Exception {
final Http2Headers headers = EmptyHttp2Headers.INSTANCE; 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(); readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(MAX_PADDING), eq(true)); verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(MAX_PADDING), eq(true));
} }
@ -212,7 +211,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void binaryHeadersWithoutPriorityShouldMatch() throws Exception { public void binaryHeadersWithoutPriorityShouldMatch() throws Exception {
final Http2Headers headers = binaryHeaders(); final Http2Headers headers = binaryHeaders();
writer.writeHeaders(ctx, STREAM_ID, headers, 0, true, ctx.newPromise()); writer.writeHeaders(ctx, STREAM_ID, headers, 0, true);
readFrames(); readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(0), eq(true)); verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(0), eq(true));
} }
@ -220,7 +219,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void headersFrameWithoutPriorityShouldMatch() throws Exception { public void headersFrameWithoutPriorityShouldMatch() throws Exception {
final Http2Headers headers = headers(); final Http2Headers headers = headers();
writer.writeHeaders(ctx, STREAM_ID, headers, 0, true, ctx.newPromise()); writer.writeHeaders(ctx, STREAM_ID, headers, 0, true);
readFrames(); readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(0), eq(true)); verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(0), eq(true));
} }
@ -228,7 +227,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void headersFrameWithPriorityShouldMatch() throws Exception { public void headersFrameWithPriorityShouldMatch() throws Exception {
final Http2Headers headers = headers(); 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(); readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(4), eq((short) 255), verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(4), eq((short) 255),
eq(true), eq(0), eq(true)); eq(true), eq(0), eq(true));
@ -237,7 +236,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void headersWithPaddingWithoutPriorityShouldMatch() throws Exception { public void headersWithPaddingWithoutPriorityShouldMatch() throws Exception {
final Http2Headers headers = headers(); 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(); readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(MAX_PADDING), eq(true)); verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(MAX_PADDING), eq(true));
} }
@ -245,7 +244,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void headersWithPaddingWithPriorityShouldMatch() throws Exception { public void headersWithPaddingWithPriorityShouldMatch() throws Exception {
final Http2Headers headers = headers(); 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(); readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(2), eq((short) 3), eq(true), verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(2), eq((short) 3), eq(true),
eq(1), eq(true)); eq(1), eq(true));
@ -254,7 +253,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void continuedHeadersShouldMatch() throws Exception { public void continuedHeadersShouldMatch() throws Exception {
final Http2Headers headers = largeHeaders(); 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(); readFrames();
verify(listener) verify(listener)
.onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(2), eq((short) 3), eq(true), eq(0), eq(true)); .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 @Test
public void continuedHeadersWithPaddingShouldMatch() throws Exception { public void continuedHeadersWithPaddingShouldMatch() throws Exception {
final Http2Headers headers = largeHeaders(); 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(); readFrames();
verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(2), eq((short) 3), eq(true), verify(listener).onHeadersRead(eq(ctx), eq(STREAM_ID), eq(headers), eq(2), eq((short) 3), eq(true),
eq(MAX_PADDING), eq(true)); eq(MAX_PADDING), eq(true));
@ -275,7 +274,7 @@ public class Http2FrameRoundtripTest {
final int maxListSize = 100; final int maxListSize = 100;
reader.configuration().headersConfiguration().maxHeaderListSize(maxListSize, maxListSize); reader.configuration().headersConfiguration().maxHeaderListSize(maxListSize, maxListSize);
final Http2Headers headers = headersOfSize(maxListSize + 1); 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() { assertThrows(Http2Exception.class, new Executable() {
@Override @Override
public void execute() throws Throwable { public void execute() throws Throwable {
@ -290,7 +289,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void emptyPushPromiseShouldMatch() throws Exception { public void emptyPushPromiseShouldMatch() throws Exception {
final Http2Headers headers = EmptyHttp2Headers.INSTANCE; final Http2Headers headers = EmptyHttp2Headers.INSTANCE;
writer.writePushPromise(ctx, STREAM_ID, 2, headers, 0, ctx.newPromise()); writer.writePushPromise(ctx, STREAM_ID, 2, headers, 0);
readFrames(); readFrames();
verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(2), eq(headers), eq(0)); verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(2), eq(headers), eq(0));
} }
@ -298,7 +297,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void pushPromiseFrameShouldMatch() throws Exception { public void pushPromiseFrameShouldMatch() throws Exception {
final Http2Headers headers = headers(); final Http2Headers headers = headers();
writer.writePushPromise(ctx, STREAM_ID, 1, headers, 5, ctx.newPromise()); writer.writePushPromise(ctx, STREAM_ID, 1, headers, 5);
readFrames(); readFrames();
verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(1), eq(headers), eq(5)); verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(1), eq(headers), eq(5));
} }
@ -306,7 +305,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void pushPromiseWithPaddingShouldMatch() throws Exception { public void pushPromiseWithPaddingShouldMatch() throws Exception {
final Http2Headers headers = headers(); 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(); readFrames();
verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(2), eq(headers), eq(MAX_PADDING)); verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(2), eq(headers), eq(MAX_PADDING));
} }
@ -314,7 +313,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void continuedPushPromiseShouldMatch() throws Exception { public void continuedPushPromiseShouldMatch() throws Exception {
final Http2Headers headers = largeHeaders(); final Http2Headers headers = largeHeaders();
writer.writePushPromise(ctx, STREAM_ID, 2, headers, 0, ctx.newPromise()); writer.writePushPromise(ctx, STREAM_ID, 2, headers, 0);
readFrames(); readFrames();
verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(2), eq(headers), eq(0)); verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(2), eq(headers), eq(0));
} }
@ -322,7 +321,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void continuedPushPromiseWithPaddingShouldMatch() throws Exception { public void continuedPushPromiseWithPaddingShouldMatch() throws Exception {
final Http2Headers headers = largeHeaders(); final Http2Headers headers = largeHeaders();
writer.writePushPromise(ctx, STREAM_ID, 2, headers, 0xFF, ctx.newPromise()); writer.writePushPromise(ctx, STREAM_ID, 2, headers, 0xFF);
readFrames(); readFrames();
verify(listener).onPushPromiseRead(eq(ctx), eq(STREAM_ID), eq(2), eq(headers), eq(0xFF)); 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 String text = "test";
final ByteBuf data = buf(text.getBytes()); 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(); readFrames();
ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class); ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);
@ -342,7 +341,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void pingFrameShouldMatch() throws Exception { public void pingFrameShouldMatch() throws Exception {
writer.writePing(ctx, false, 1234567, ctx.newPromise()); writer.writePing(ctx, false, 1234567);
readFrames(); readFrames();
ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(long.class); ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(long.class);
@ -352,7 +351,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void pingAckFrameShouldMatch() throws Exception { public void pingAckFrameShouldMatch() throws Exception {
writer.writePing(ctx, true, 1234567, ctx.newPromise()); writer.writePing(ctx, true, 1234567);
readFrames(); readFrames();
ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(long.class); ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(long.class);
@ -362,14 +361,14 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void priorityFrameShouldMatch() throws Exception { 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(); readFrames();
verify(listener).onPriorityRead(eq(ctx), eq(STREAM_ID), eq(1), eq((short) 1), eq(true)); verify(listener).onPriorityRead(eq(ctx), eq(STREAM_ID), eq(1), eq((short) 1), eq(true));
} }
@Test @Test
public void rstStreamFrameShouldMatch() throws Exception { public void rstStreamFrameShouldMatch() throws Exception {
writer.writeRstStream(ctx, STREAM_ID, ERROR_CODE, ctx.newPromise()); writer.writeRstStream(ctx, STREAM_ID, ERROR_CODE);
readFrames(); readFrames();
verify(listener).onRstStreamRead(eq(ctx), eq(STREAM_ID), eq(ERROR_CODE)); verify(listener).onRstStreamRead(eq(ctx), eq(STREAM_ID), eq(ERROR_CODE));
} }
@ -377,7 +376,7 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void emptySettingsFrameShouldMatch() throws Exception { public void emptySettingsFrameShouldMatch() throws Exception {
final Http2Settings settings = new Http2Settings(); final Http2Settings settings = new Http2Settings();
writer.writeSettings(ctx, settings, ctx.newPromise()); writer.writeSettings(ctx, settings);
readFrames(); readFrames();
verify(listener).onSettingsRead(eq(ctx), eq(settings)); verify(listener).onSettingsRead(eq(ctx), eq(settings));
} }
@ -390,21 +389,21 @@ public class Http2FrameRoundtripTest {
settings.initialWindowSize(123); settings.initialWindowSize(123);
settings.maxConcurrentStreams(456); settings.maxConcurrentStreams(456);
writer.writeSettings(ctx, settings, ctx.newPromise()); writer.writeSettings(ctx, settings);
readFrames(); readFrames();
verify(listener).onSettingsRead(eq(ctx), eq(settings)); verify(listener).onSettingsRead(eq(ctx), eq(settings));
} }
@Test @Test
public void settingsAckShouldMatch() throws Exception { public void settingsAckShouldMatch() throws Exception {
writer.writeSettingsAck(ctx, ctx.newPromise()); writer.writeSettingsAck(ctx);
readFrames(); readFrames();
verify(listener).onSettingsAckRead(eq(ctx)); verify(listener).onSettingsAckRead(eq(ctx));
} }
@Test @Test
public void windowUpdateFrameShouldMatch() throws Exception { public void windowUpdateFrameShouldMatch() throws Exception {
writer.writeWindowUpdate(ctx, STREAM_ID, WINDOW_UPDATE, ctx.newPromise()); writer.writeWindowUpdate(ctx, STREAM_ID, WINDOW_UPDATE);
readFrames(); readFrames();
verify(listener).onWindowUpdateRead(eq(ctx), eq(STREAM_ID), eq(WINDOW_UPDATE)); 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.AsciiString;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
import org.hamcrest.CoreMatchers; import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.AfterEach; 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.concurrent.atomic.AtomicReference;
import java.util.function.Consumer; 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.anyHttp2Settings;
import static io.netty.handler.codec.http2.Http2TestUtil.assertEqualsAndRelease; import static io.netty.handler.codec.http2.Http2TestUtil.assertEqualsAndRelease;
import static io.netty.handler.codec.http2.Http2TestUtil.bb; 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); Http2Settings settings = new Http2Settings().initialWindowSize(initialRemoteStreamWindow);
frameInboundWriter.writeInboundSettings(settings); frameInboundWriter.writeInboundSettings(settings);
verify(frameWriter).writeSettingsAck(any(ChannelHandlerContext.class), anyChannelPromise()); verify(frameWriter).writeSettingsAck(any(ChannelHandlerContext.class));
frameInboundWriter.writeInboundSettingsAck(); frameInboundWriter.writeInboundSettingsAck();
@ -121,7 +121,7 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
// Handshake // Handshake
verify(frameWriter).writeSettings(any(ChannelHandlerContext.class), verify(frameWriter).writeSettings(any(ChannelHandlerContext.class),
anyHttp2Settings(), anyChannelPromise()); anyHttp2Settings());
} }
@AfterEach @AfterEach
@ -151,7 +151,7 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
assertTrue(childChannel.isActive()); assertTrue(childChannel.isActive());
verify(frameWriter).writeFrame(any(ChannelHandlerContext.class), eq((byte) 99), eqStreamId(childChannel), 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) { private Http2StreamChannel newInboundStream(int streamId, boolean endStream, final ChannelHandler childHandler) {
@ -552,14 +552,13 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
childChannel.close(); childChannel.close();
verify(frameWriter).writeRstStream(any(ChannelHandlerContext.class), verify(frameWriter).writeRstStream(any(ChannelHandlerContext.class),
eqStreamId(childChannel), eq(Http2Error.CANCEL.code()), anyChannelPromise()); eqStreamId(childChannel), eq(Http2Error.CANCEL.code()));
} }
@Test @Test
public void outboundStreamShouldNotWriteResetFrameOnClose_IfStreamDidntExist() { public void outboundStreamShouldNotWriteResetFrameOnClose_IfStreamDidntExist() {
when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(), when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(),
any(Http2Headers.class), anyInt(), anyBoolean(), any(Http2Headers.class), anyInt(), anyBoolean())).thenAnswer(new Answer<Future<Void>>() {
any(Promise.class))).thenAnswer(new Answer<Future<Void>>() {
private boolean headersWritten; private boolean headersWritten;
@Override @Override
@ -568,9 +567,9 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
// refuses to allocate a new stream due to having received a GOAWAY. // refuses to allocate a new stream due to having received a GOAWAY.
if (!headersWritten) { if (!headersWritten) {
headersWritten = true; 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(); childChannel.close();
// The channel was never active so we should not generate a RST frame. // The channel was never active so we should not generate a RST frame.
verify(frameWriter, never()).writeRstStream(any(ChannelHandlerContext.class), verify(frameWriter, never()).writeRstStream(any(ChannelHandlerContext.class),
eqStreamId(childChannel), anyLong(), anyChannelPromise()); eqStreamId(childChannel), anyLong());
assertTrue(parentChannel.outboundMessages().isEmpty()); 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. // A RST_STREAM frame should NOT be emitted, as we received a RST_STREAM.
verify(frameWriter, never()).writeRstStream(any(ChannelHandlerContext.class), eqStreamId(channel), verify(frameWriter, never()).writeRstStream(any(ChannelHandlerContext.class), eqStreamId(channel),
anyLong(), anyChannelPromise()); anyLong());
} }
@Test @Test
@ -633,11 +632,9 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
Http2Headers headers = new DefaultHttp2Headers(); Http2Headers headers = new DefaultHttp2Headers();
when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(), when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(),
eq(headers), anyInt(), anyBoolean(), eq(headers), anyInt(), anyBoolean())).thenAnswer(invocationOnMock ->
any(Promise.class))).thenAnswer(invocationOnMock -> { ImmediateEventExecutor.INSTANCE.newFailedFuture(
return ((Promise<Void>) invocationOnMock.getArgument(5)).setFailure( new StreamException(childChannel.stream().id(), Http2Error.STREAM_CLOSED, "Stream Closed")));
new StreamException(childChannel.stream().id(), Http2Error.STREAM_CLOSED, "Stream Closed"));
});
final Future<Void> future = childChannel.writeAndFlush( final Future<Void> future = childChannel.writeAndFlush(
new DefaultHttp2HeadersFrame(new DefaultHttp2Headers())); 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. // An active outbound stream should emit a RST_STREAM frame.
verify(frameWriter).writeRstStream(any(ChannelHandlerContext.class), eqStreamId(childChannel), verify(frameWriter).writeRstStream(any(ChannelHandlerContext.class), eqStreamId(childChannel),
anyLong(), anyChannelPromise()); anyLong());
assertFalse(childChannel.isOpen()); assertFalse(childChannel.isOpen());
assertFalse(childChannel.isActive()); assertFalse(childChannel.isActive());
@ -698,10 +695,8 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
Http2Headers headers = new DefaultHttp2Headers(); Http2Headers headers = new DefaultHttp2Headers();
when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(), when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(),
eq(headers), anyInt(), anyBoolean(), any(Promise.class))).thenAnswer(invocationOnMock -> { eq(headers), anyInt(), anyBoolean())).thenAnswer(invocationOnMock ->
return ((Promise<Void>) invocationOnMock.getArgument(5)).setFailure( ImmediateEventExecutor.INSTANCE.newFailedFuture(new Http2NoMoreStreamIdsException()));
new Http2NoMoreStreamIdsException());
});
final Future<Void> future = childChannel.writeAndFlush(new DefaultHttp2HeadersFrame(headers)); final Future<Void> future = childChannel.writeAndFlush(new DefaultHttp2HeadersFrame(headers));
parentChannel.flush(); parentChannel.flush();
@ -782,9 +777,8 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
Http2Headers headers = new DefaultHttp2Headers(); Http2Headers headers = new DefaultHttp2Headers();
when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(), when(frameWriter.writeHeaders(any(ChannelHandlerContext.class), anyInt(),
eq(headers), anyInt(), anyBoolean(), eq(headers), anyInt(), anyBoolean())).thenAnswer(invocationOnMock -> {
any(Promise.class))).thenAnswer(invocationOnMock -> { Promise<Void> promise = ImmediateEventExecutor.INSTANCE.newPromise();
Promise<Void> promise = invocationOnMock.getArgument(5);
writePromises.offer(promise); writePromises.offer(promise);
return promise; return promise;
}); });
@ -1305,7 +1299,7 @@ public abstract class Http2MultiplexTest<C extends Http2FrameCodec> {
assertTrue(flushSniffer.checkFlush()); assertTrue(flushSniffer.checkFlush());
verify(frameWriter, never()) 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. // only the first one was read because it was legacy auto-read behavior.
verifyFramesMultiplexedToCorrectChannel(childChannel, inboundHandler, 1); verifyFramesMultiplexedToCorrectChannel(childChannel, inboundHandler, 1);
assertFalse(flushSniffer.checkFlush()); 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. // connection will collect the bytes and decide not to send a wire level frame until more are consumed.
assertTrue(flushSniffer.checkFlush()); assertTrue(flushSniffer.checkFlush());
verify(frameWriter, never()) 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. // Call read one more time which should trigger the writing of the flow control update.
childChannel.read(); childChannel.read();
verify(frameWriter) verify(frameWriter)
.writeWindowUpdate(any(ChannelHandlerContext.class), eq(0), eq(32 * 1024), anyChannelPromise()); .writeWindowUpdate(any(ChannelHandlerContext.class), eq(0), eq(32 * 1024));
verify(frameWriter) verify(frameWriter)
.writeWindowUpdate(any(ChannelHandlerContext.class), eq(childChannel.stream().id()), .writeWindowUpdate(any(ChannelHandlerContext.class), eq(childChannel.stream().id()),
eq(32 * 1024), anyChannelPromise()); eq(32 * 1024));
assertTrue(flushSniffer.checkFlush()); assertTrue(flushSniffer.checkFlush());
} }

View File

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

View File

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

View File

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

View File

@ -101,14 +101,14 @@ public class Http2FrameWriterDataBenchmark extends AbstractMicrobenchmark {
@Benchmark @Benchmark
@BenchmarkMode(Mode.AverageTime) @BenchmarkMode(Mode.AverageTime)
public void newWriter() { public void newWriter() {
writer.writeData(ctx, 3, payload.retain(), padding, true, ctx.newPromise()); writer.writeData(ctx, 3, payload.retain(), padding, true);
ctx.flush(); ctx.flush();
} }
@Benchmark @Benchmark
@BenchmarkMode(Mode.AverageTime) @BenchmarkMode(Mode.AverageTime)
public void oldWriter() { public void oldWriter() {
oldWriter.writeData(ctx, 3, payload.retain(), padding, true, ctx.newPromise()); oldWriter.writeData(ctx, 3, payload.retain(), padding, true);
ctx.flush(); ctx.flush();
} }
@ -118,9 +118,9 @@ public class Http2FrameWriterDataBenchmark extends AbstractMicrobenchmark {
private final int maxFrameSize = DEFAULT_MAX_FRAME_SIZE; private final int maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
@Override @Override
public Future<Void> writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data, 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 = final Http2CodecUtil.SimpleChannelPromiseAggregator promiseAggregator =
new Http2CodecUtil.SimpleChannelPromiseAggregator(promise, ctx.executor()); new Http2CodecUtil.SimpleChannelPromiseAggregator(ctx.newPromise(), ctx.executor());
final DataFrameHeader header = new DataFrameHeader(ctx, streamId); final DataFrameHeader header = new DataFrameHeader(ctx, streamId);
boolean needToReleaseHeaders = true; boolean needToReleaseHeaders = true;
boolean needToReleaseData = 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) { private void sendResponse(ChannelHandlerContext ctx, int streamId, ByteBuf payload) {
// Send a frame for the response status // Send a frame for the response status
Http2Headers headers = new DefaultHttp2Headers().status(OK.codeAsText()); Http2Headers headers = new DefaultHttp2Headers().status(OK.codeAsText());
encoder().writeHeaders(ctx, streamId, headers, 0, false, ctx.newPromise()); encoder().writeHeaders(ctx, streamId, headers, 0, false);
encoder().writeData(ctx, streamId, payload, 0, true, ctx.newPromise()); encoder().writeData(ctx, streamId, payload, 0, true);
// no need to call flush as channelReadComplete(...) will take care of it. // no need to call flush as channelReadComplete(...) will take care of it.
} }