2015-01-20 01:48:11 +01:00
|
|
|
/*
|
|
|
|
* Copyright 2014 The Netty Project
|
|
|
|
*
|
|
|
|
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
|
|
|
|
* "License"); you may not use this file except in compliance with the License. You may obtain a
|
|
|
|
* copy of the License at:
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
|
|
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
|
|
* or implied. See the License for the specific language governing permissions and limitations under
|
|
|
|
* the License.
|
|
|
|
*/
|
|
|
|
package io.netty.handler.codec.http2;
|
|
|
|
|
|
|
|
import io.netty.buffer.ByteBuf;
|
|
|
|
import io.netty.channel.ChannelFuture;
|
|
|
|
import io.netty.channel.ChannelFutureListener;
|
|
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
|
|
import io.netty.channel.ChannelPromise;
|
2015-06-30 21:31:08 +02:00
|
|
|
import io.netty.channel.CoalescingBufferQueue;
|
2017-07-11 23:53:49 +02:00
|
|
|
import io.netty.handler.codec.http.HttpStatusClass;
|
2016-04-12 14:22:41 +02:00
|
|
|
import io.netty.util.internal.UnstableApi;
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
import java.util.ArrayDeque;
|
|
|
|
|
2017-07-11 23:53:49 +02:00
|
|
|
import static io.netty.handler.codec.http.HttpStatusClass.INFORMATIONAL;
|
2016-01-29 09:45:12 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
|
|
|
|
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
|
|
|
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
|
|
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
2016-08-09 22:10:02 +02:00
|
|
|
import static java.lang.Integer.MAX_VALUE;
|
2016-01-29 09:45:12 +01:00
|
|
|
import static java.lang.Math.min;
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
/**
|
|
|
|
* Default implementation of {@link Http2ConnectionEncoder}.
|
|
|
|
*/
|
2016-04-12 14:22:41 +02:00
|
|
|
@UnstableApi
|
2015-01-20 01:48:11 +01:00
|
|
|
public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
|
|
|
|
private final Http2FrameWriter frameWriter;
|
|
|
|
private final Http2Connection connection;
|
2015-03-27 23:37:20 +01:00
|
|
|
private Http2LifecycleManager lifecycleManager;
|
2015-01-20 01:48:11 +01:00
|
|
|
// We prefer ArrayDeque to LinkedList because later will produce more GC.
|
|
|
|
// This initial capacity is plenty for SETTINGS traffic.
|
|
|
|
private final ArrayDeque<Http2Settings> outstandingLocalSettingsQueue = new ArrayDeque<Http2Settings>(4);
|
|
|
|
|
2015-03-27 23:37:20 +01:00
|
|
|
public DefaultHttp2ConnectionEncoder(Http2Connection connection, Http2FrameWriter frameWriter) {
|
|
|
|
this.connection = checkNotNull(connection, "connection");
|
|
|
|
this.frameWriter = checkNotNull(frameWriter, "frameWriter");
|
2015-01-20 01:48:11 +01:00
|
|
|
if (connection.remote().flowController() == null) {
|
2015-01-23 20:32:17 +01:00
|
|
|
connection.remote().flowController(new DefaultHttp2RemoteFlowController(connection));
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-27 23:37:20 +01:00
|
|
|
@Override
|
|
|
|
public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
|
|
|
|
this.lifecycleManager = checkNotNull(lifecycleManager, "lifecycleManager");
|
|
|
|
}
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
@Override
|
|
|
|
public Http2FrameWriter frameWriter() {
|
|
|
|
return frameWriter;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Http2Connection connection() {
|
|
|
|
return connection;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public final Http2RemoteFlowController flowController() {
|
|
|
|
return connection().remote().flowController();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void remoteSettings(Http2Settings settings) throws Http2Exception {
|
|
|
|
Boolean pushEnabled = settings.pushEnabled();
|
|
|
|
Http2FrameWriter.Configuration config = configuration();
|
2017-01-14 02:09:44 +01:00
|
|
|
Http2HeadersEncoder.Configuration outboundHeaderConfig = config.headersConfiguration();
|
2015-01-20 01:48:11 +01:00
|
|
|
Http2FrameSizePolicy outboundFrameSizePolicy = config.frameSizePolicy();
|
|
|
|
if (pushEnabled != null) {
|
2015-07-07 18:06:32 +02:00
|
|
|
if (!connection.isServer() && pushEnabled) {
|
|
|
|
throw connectionError(PROTOCOL_ERROR,
|
|
|
|
"Client received a value of ENABLE_PUSH specified to other than 0");
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
connection.remote().allowPushTo(pushEnabled);
|
|
|
|
}
|
|
|
|
|
|
|
|
Long maxConcurrentStreams = settings.maxConcurrentStreams();
|
|
|
|
if (maxConcurrentStreams != null) {
|
2017-01-24 21:50:39 +01:00
|
|
|
connection.local().maxActiveStreams((int) min(maxConcurrentStreams, MAX_VALUE));
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Long headerTableSize = settings.headerTableSize();
|
|
|
|
if (headerTableSize != null) {
|
2017-01-14 02:09:44 +01:00
|
|
|
outboundHeaderConfig.maxHeaderTableSize((int) min(headerTableSize, MAX_VALUE));
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
2016-09-16 15:57:33 +02:00
|
|
|
Long maxHeaderListSize = settings.maxHeaderListSize();
|
2015-01-20 01:48:11 +01:00
|
|
|
if (maxHeaderListSize != null) {
|
2017-01-14 02:09:44 +01:00
|
|
|
outboundHeaderConfig.maxHeaderListSize(maxHeaderListSize);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Integer maxFrameSize = settings.maxFrameSize();
|
|
|
|
if (maxFrameSize != null) {
|
|
|
|
outboundFrameSizePolicy.maxFrameSize(maxFrameSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
Integer initialWindowSize = settings.initialWindowSize();
|
|
|
|
if (initialWindowSize != null) {
|
|
|
|
flowController().initialWindowSize(initialWindowSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeData(final ChannelHandlerContext ctx, final int streamId, ByteBuf data, int padding,
|
|
|
|
final boolean endOfStream, ChannelPromise promise) {
|
2015-01-23 20:32:17 +01:00
|
|
|
final Http2Stream stream;
|
2015-01-20 01:48:11 +01:00
|
|
|
try {
|
2015-04-21 23:18:02 +02:00
|
|
|
stream = requireStream(streamId);
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
// Verify that the stream is in the appropriate state for sending DATA frames.
|
|
|
|
switch (stream.state()) {
|
|
|
|
case OPEN:
|
|
|
|
case HALF_CLOSED_REMOTE:
|
|
|
|
// Allowed sending DATA frames in these states.
|
|
|
|
break;
|
|
|
|
default:
|
2017-06-27 21:27:42 +02:00
|
|
|
throw new IllegalStateException("Stream " + stream.id() + " in unexpected state " + stream.state());
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
} catch (Throwable e) {
|
|
|
|
data.release();
|
|
|
|
return promise.setFailure(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hand control of the frame to the flow controller.
|
2015-06-30 19:10:17 +02:00
|
|
|
flowController().addFlowControlled(stream,
|
|
|
|
new FlowControlledData(stream, data, padding, endOfStream, promise));
|
2015-01-23 20:32:17 +01:00
|
|
|
return promise;
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
|
|
|
|
boolean endStream, ChannelPromise promise) {
|
|
|
|
return writeHeaders(ctx, streamId, headers, 0, DEFAULT_PRIORITY_WEIGHT, false, padding, endStream, promise);
|
|
|
|
}
|
|
|
|
|
2017-07-11 23:53:49 +02:00
|
|
|
private static boolean validateHeadersSentState(Http2Stream stream, Http2Headers headers, boolean isServer,
|
|
|
|
boolean endOfStream) {
|
|
|
|
boolean isInformational = isServer && HttpStatusClass.valueOf(headers.status()) == INFORMATIONAL;
|
|
|
|
if ((isInformational || !endOfStream) && stream.isHeadersSent() || stream.isTrailersSent()) {
|
|
|
|
throw new IllegalStateException("Stream " + stream.id() + " sent too many headers EOS: " + endOfStream);
|
|
|
|
}
|
|
|
|
return isInformational;
|
|
|
|
}
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
@Override
|
|
|
|
public ChannelFuture writeHeaders(final ChannelHandlerContext ctx, final int streamId,
|
|
|
|
final Http2Headers headers, final int streamDependency, final short weight,
|
2016-09-12 19:40:26 +02:00
|
|
|
final boolean exclusive, final int padding, final boolean endOfStream, ChannelPromise promise) {
|
2015-01-20 01:48:11 +01:00
|
|
|
try {
|
2015-01-23 20:32:17 +01:00
|
|
|
Http2Stream stream = connection.stream(streamId);
|
2015-01-20 01:48:11 +01:00
|
|
|
if (stream == null) {
|
2017-06-27 21:27:42 +02:00
|
|
|
try {
|
|
|
|
stream = connection.local().createStream(streamId, endOfStream);
|
|
|
|
} catch (Http2Exception cause) {
|
|
|
|
if (connection.remote().mayHaveCreatedStream(streamId)) {
|
|
|
|
promise.tryFailure(new IllegalStateException("Stream no longer exists: " + streamId, cause));
|
|
|
|
return promise;
|
|
|
|
}
|
|
|
|
throw cause;
|
|
|
|
}
|
2015-04-22 23:35:31 +02:00
|
|
|
} else {
|
|
|
|
switch (stream.state()) {
|
|
|
|
case RESERVED_LOCAL:
|
|
|
|
stream.open(endOfStream);
|
|
|
|
break;
|
|
|
|
case OPEN:
|
|
|
|
case HALF_CLOSED_REMOTE:
|
|
|
|
// Allowed sending headers in these states.
|
|
|
|
break;
|
|
|
|
default:
|
2017-06-27 21:27:42 +02:00
|
|
|
throw new IllegalStateException("Stream " + stream.id() + " in unexpected state " +
|
|
|
|
stream.state());
|
2015-04-22 23:35:31 +02:00
|
|
|
}
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
2016-01-29 09:45:12 +01:00
|
|
|
// Trailing headers must go through flow control if there are other frames queued in flow control
|
|
|
|
// for this stream.
|
|
|
|
Http2RemoteFlowController flowController = flowController();
|
|
|
|
if (!endOfStream || !flowController.hasFlowControlled(stream)) {
|
2017-07-11 23:53:49 +02:00
|
|
|
boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream);
|
2016-01-29 09:45:12 +01:00
|
|
|
if (endOfStream) {
|
|
|
|
final Http2Stream finalStream = stream;
|
2016-09-12 19:40:26 +02:00
|
|
|
final ChannelFutureListener closeStreamLocalListener = new ChannelFutureListener() {
|
2016-01-29 09:45:12 +01:00
|
|
|
@Override
|
|
|
|
public void operationComplete(ChannelFuture future) throws Exception {
|
2016-09-12 19:40:26 +02:00
|
|
|
lifecycleManager.closeStreamLocal(finalStream, future);
|
2016-01-29 09:45:12 +01:00
|
|
|
}
|
2016-09-12 19:40:26 +02:00
|
|
|
};
|
|
|
|
promise = promise.unvoid().addListener(closeStreamLocalListener);
|
2016-01-29 09:45:12 +01:00
|
|
|
}
|
2017-07-11 23:53:49 +02:00
|
|
|
|
2016-11-23 20:32:41 +01:00
|
|
|
ChannelFuture future = frameWriter.writeHeaders(ctx, streamId, headers, streamDependency,
|
|
|
|
weight, exclusive, padding, endOfStream, promise);
|
2017-01-26 18:03:42 +01:00
|
|
|
// Writing headers may fail during the encode state if they violate HPACK limits.
|
|
|
|
Throwable failureCause = future.cause();
|
|
|
|
if (failureCause == null) {
|
|
|
|
// Synchronously set the headersSent flag to ensure that we do not subsequently write
|
|
|
|
// other headers containing pseudo-header fields.
|
2018-01-24 13:52:10 +01:00
|
|
|
//
|
|
|
|
// This just sets internal stream state which is used elsewhere in the codec and doesn't
|
|
|
|
// necessarily mean the write will complete successfully.
|
2017-07-11 23:53:49 +02:00
|
|
|
stream.headersSent(isInformational);
|
2018-01-24 13:52:10 +01:00
|
|
|
|
|
|
|
if (!future.isSuccess()) {
|
|
|
|
// Either the future is not done or failed in the meantime.
|
|
|
|
notifyLifecycleManagerOnError(future, ctx);
|
|
|
|
}
|
2017-01-26 18:03:42 +01:00
|
|
|
} else {
|
2018-01-25 22:42:28 +01:00
|
|
|
lifecycleManager.onError(ctx, true, failureCause);
|
2017-01-26 18:03:42 +01:00
|
|
|
}
|
2016-11-23 20:32:41 +01:00
|
|
|
|
|
|
|
return future;
|
2016-01-29 09:45:12 +01:00
|
|
|
} else {
|
|
|
|
// Pass headers to the flow-controller so it can maintain their sequence relative to DATA frames.
|
|
|
|
flowController.addFlowControlled(stream,
|
|
|
|
new FlowControlledHeaders(stream, headers, streamDependency, weight, exclusive, padding,
|
2017-01-24 21:50:39 +01:00
|
|
|
true, promise));
|
2016-01-29 09:45:12 +01:00
|
|
|
return promise;
|
|
|
|
}
|
2017-01-14 02:09:44 +01:00
|
|
|
} catch (Throwable t) {
|
2018-01-25 22:42:28 +01:00
|
|
|
lifecycleManager.onError(ctx, true, t);
|
2017-01-14 02:09:44 +01:00
|
|
|
promise.tryFailure(t);
|
|
|
|
return promise;
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writePriority(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
|
|
|
|
boolean exclusive, ChannelPromise promise) {
|
2015-10-30 17:14:52 +01:00
|
|
|
return frameWriter.writePriority(ctx, streamId, streamDependency, weight, exclusive, promise);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode,
|
|
|
|
ChannelPromise promise) {
|
|
|
|
// Delegate to the lifecycle manager for proper updating of connection state.
|
2015-04-02 23:39:46 +02:00
|
|
|
return lifecycleManager.resetStream(ctx, streamId, errorCode, promise);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeSettings(ChannelHandlerContext ctx, Http2Settings settings,
|
|
|
|
ChannelPromise promise) {
|
|
|
|
outstandingLocalSettingsQueue.add(settings);
|
|
|
|
try {
|
|
|
|
Boolean pushEnabled = settings.pushEnabled();
|
|
|
|
if (pushEnabled != null && connection.isServer()) {
|
|
|
|
throw connectionError(PROTOCOL_ERROR, "Server sending SETTINGS frame with ENABLE_PUSH specified");
|
|
|
|
}
|
|
|
|
} catch (Throwable e) {
|
|
|
|
return promise.setFailure(e);
|
|
|
|
}
|
|
|
|
|
2015-10-30 17:14:52 +01:00
|
|
|
return frameWriter.writeSettings(ctx, settings, promise);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) {
|
2015-10-30 17:14:52 +01:00
|
|
|
return frameWriter.writeSettingsAck(ctx, promise);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2015-03-20 02:36:24 +01:00
|
|
|
public ChannelFuture writePing(ChannelHandlerContext ctx, boolean ack, ByteBuf data, ChannelPromise promise) {
|
2015-10-30 17:14:52 +01:00
|
|
|
return frameWriter.writePing(ctx, ack, data, promise);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
|
|
|
|
Http2Headers headers, int padding, ChannelPromise promise) {
|
|
|
|
try {
|
2015-03-20 02:36:24 +01:00
|
|
|
if (connection.goAwayReceived()) {
|
|
|
|
throw connectionError(PROTOCOL_ERROR, "Sending PUSH_PROMISE after GO_AWAY received.");
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
2015-04-21 23:18:02 +02:00
|
|
|
Http2Stream stream = requireStream(streamId);
|
2015-03-20 02:36:24 +01:00
|
|
|
// Reserve the promised stream.
|
2015-01-20 01:48:11 +01:00
|
|
|
connection.local().reservePushStream(promisedStreamId, stream);
|
|
|
|
|
2017-01-14 02:09:44 +01:00
|
|
|
ChannelFuture future = frameWriter.writePushPromise(ctx, streamId, promisedStreamId, headers, padding,
|
|
|
|
promise);
|
2017-01-26 18:03:42 +01:00
|
|
|
// Writing headers may fail during the encode state if they violate HPACK limits.
|
|
|
|
Throwable failureCause = future.cause();
|
|
|
|
if (failureCause == null) {
|
2018-01-24 13:52:10 +01:00
|
|
|
// This just sets internal stream state which is used elsewhere in the codec and doesn't
|
|
|
|
// necessarily mean the write will complete successfully.
|
2017-01-26 18:03:42 +01:00
|
|
|
stream.pushPromiseSent();
|
2018-01-24 13:52:10 +01:00
|
|
|
|
|
|
|
if (!future.isSuccess()) {
|
|
|
|
// Either the future is not done or failed in the meantime.
|
|
|
|
notifyLifecycleManagerOnError(future, ctx);
|
|
|
|
}
|
2017-01-26 18:03:42 +01:00
|
|
|
} else {
|
2018-01-25 22:42:28 +01:00
|
|
|
lifecycleManager.onError(ctx, true, failureCause);
|
2017-01-26 18:03:42 +01:00
|
|
|
}
|
2017-01-14 02:09:44 +01:00
|
|
|
return future;
|
|
|
|
} catch (Throwable t) {
|
2018-01-25 22:42:28 +01:00
|
|
|
lifecycleManager.onError(ctx, true, t);
|
2017-01-14 02:09:44 +01:00
|
|
|
promise.tryFailure(t);
|
|
|
|
return promise;
|
|
|
|
}
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData,
|
|
|
|
ChannelPromise promise) {
|
2015-04-02 23:39:46 +02:00
|
|
|
return lifecycleManager.goAway(ctx, lastStreamId, errorCode, debugData, promise);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeWindowUpdate(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement,
|
|
|
|
ChannelPromise promise) {
|
|
|
|
return promise.setFailure(new UnsupportedOperationException("Use the Http2[Inbound|Outbound]FlowController" +
|
|
|
|
" objects to control window sizes"));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
|
|
|
|
ByteBuf payload, ChannelPromise promise) {
|
|
|
|
return frameWriter.writeFrame(ctx, frameType, streamId, flags, payload, promise);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void close() {
|
|
|
|
frameWriter.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Http2Settings pollSentSettings() {
|
|
|
|
return outstandingLocalSettingsQueue.poll();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Configuration configuration() {
|
|
|
|
return frameWriter.configuration();
|
|
|
|
}
|
2015-01-23 20:32:17 +01:00
|
|
|
|
2015-04-21 23:18:02 +02:00
|
|
|
private Http2Stream requireStream(int streamId) {
|
|
|
|
Http2Stream stream = connection.stream(streamId);
|
|
|
|
if (stream == null) {
|
|
|
|
final String message;
|
|
|
|
if (connection.streamMayHaveExisted(streamId)) {
|
|
|
|
message = "Stream no longer exists: " + streamId;
|
|
|
|
} else {
|
|
|
|
message = "Stream does not exist: " + streamId;
|
|
|
|
}
|
|
|
|
throw new IllegalArgumentException(message);
|
|
|
|
}
|
|
|
|
return stream;
|
|
|
|
}
|
|
|
|
|
2015-01-23 20:32:17 +01:00
|
|
|
/**
|
|
|
|
* Wrap a DATA frame so it can be written subject to flow-control. Note that this implementation assumes it
|
|
|
|
* only writes padding once for the entire payload as opposed to writing it once per-frame. This makes the
|
|
|
|
* {@link #size} calculation deterministic thereby greatly simplifying the implementation.
|
|
|
|
* <p>
|
|
|
|
* If frame-splitting is required to fit within max-frame-size and flow-control constraints we ensure that
|
|
|
|
* the passed promise is not completed until last frame write.
|
|
|
|
* </p>
|
|
|
|
*/
|
|
|
|
private final class FlowControlledData extends FlowControlledBase {
|
2015-06-30 21:31:08 +02:00
|
|
|
private final CoalescingBufferQueue queue;
|
2016-03-08 01:20:45 +01:00
|
|
|
private int dataSize;
|
2015-01-23 20:32:17 +01:00
|
|
|
|
2016-08-24 12:25:59 +02:00
|
|
|
FlowControlledData(Http2Stream stream, ByteBuf buf, int padding, boolean endOfStream,
|
2015-06-30 19:10:17 +02:00
|
|
|
ChannelPromise promise) {
|
|
|
|
super(stream, padding, endOfStream, promise);
|
|
|
|
queue = new CoalescingBufferQueue(promise.channel());
|
2015-06-30 21:31:08 +02:00
|
|
|
queue.add(buf, promise);
|
2016-03-08 01:20:45 +01:00
|
|
|
dataSize = queue.readableBytes();
|
2015-01-23 20:32:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int size() {
|
2016-03-08 01:20:45 +01:00
|
|
|
return dataSize + padding;
|
2015-01-23 20:32:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2015-06-30 19:10:17 +02:00
|
|
|
public void error(ChannelHandlerContext ctx, Throwable cause) {
|
2015-06-30 21:31:08 +02:00
|
|
|
queue.releaseAndFailAll(cause);
|
2016-03-08 01:20:45 +01:00
|
|
|
// Don't update dataSize because we need to ensure the size() method returns a consistent size even after
|
|
|
|
// error so we don't invalidate flow control when returning bytes to flow control.
|
2018-01-25 22:42:28 +01:00
|
|
|
lifecycleManager.onError(ctx, true, cause);
|
2015-01-23 20:32:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2015-06-30 19:10:17 +02:00
|
|
|
public void write(ChannelHandlerContext ctx, int allowedBytes) {
|
2015-11-19 05:46:11 +01:00
|
|
|
int queuedData = queue.readableBytes();
|
2016-08-24 12:25:59 +02:00
|
|
|
if (!endOfStream) {
|
|
|
|
if (queuedData == 0) {
|
|
|
|
// There's no need to write any data frames because there are only empty data frames in the queue
|
2017-11-04 01:09:42 +01:00
|
|
|
// and it is not end of stream yet. Just complete their promises by getting the buffer corresponding
|
|
|
|
// to 0 bytes and writing it to the channel (to preserve notification order).
|
2016-08-24 12:25:59 +02:00
|
|
|
ChannelPromise writePromise = ctx.newPromise().addListener(this);
|
2017-11-04 01:09:42 +01:00
|
|
|
ctx.write(queue.remove(0, writePromise), writePromise);
|
2016-08-24 12:25:59 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (allowedBytes == 0) {
|
|
|
|
return;
|
|
|
|
}
|
2015-04-23 23:23:23 +02:00
|
|
|
}
|
2015-11-19 05:46:11 +01:00
|
|
|
|
|
|
|
// Determine how much data to write.
|
2017-04-19 22:37:03 +02:00
|
|
|
int writableData = min(queuedData, allowedBytes);
|
2015-11-19 05:46:11 +01:00
|
|
|
ChannelPromise writePromise = ctx.newPromise().addListener(this);
|
2017-04-19 22:37:03 +02:00
|
|
|
ByteBuf toWrite = queue.remove(writableData, writePromise);
|
2016-03-08 01:20:45 +01:00
|
|
|
dataSize = queue.readableBytes();
|
2015-11-19 05:46:11 +01:00
|
|
|
|
|
|
|
// Determine how much padding to write.
|
2017-04-19 22:37:03 +02:00
|
|
|
int writablePadding = min(allowedBytes - writableData, padding);
|
|
|
|
padding -= writablePadding;
|
2015-11-19 05:46:11 +01:00
|
|
|
|
|
|
|
// Write the frame(s).
|
2017-04-19 22:37:03 +02:00
|
|
|
frameWriter().writeData(ctx, stream.id(), toWrite, writablePadding,
|
2015-11-19 05:46:11 +01:00
|
|
|
endOfStream && size() == 0, writePromise);
|
2015-01-23 20:32:17 +01:00
|
|
|
}
|
2015-06-04 20:55:18 +02:00
|
|
|
|
|
|
|
@Override
|
2015-06-30 19:10:17 +02:00
|
|
|
public boolean merge(ChannelHandlerContext ctx, Http2RemoteFlowController.FlowControlled next) {
|
2016-01-21 03:18:34 +01:00
|
|
|
FlowControlledData nextData;
|
|
|
|
if (FlowControlledData.class != next.getClass() ||
|
2016-08-09 22:10:02 +02:00
|
|
|
MAX_VALUE - (nextData = (FlowControlledData) next).size() < size()) {
|
2015-06-04 20:55:18 +02:00
|
|
|
return false;
|
|
|
|
}
|
2015-06-30 21:31:08 +02:00
|
|
|
nextData.queue.copyTo(queue);
|
2016-03-08 01:20:45 +01:00
|
|
|
dataSize = queue.readableBytes();
|
2015-06-04 20:55:18 +02:00
|
|
|
// Given that we're merging data into a frame it doesn't really make sense to accumulate padding.
|
2015-06-30 21:31:08 +02:00
|
|
|
padding = Math.max(padding, nextData.padding);
|
2015-06-04 20:55:18 +02:00
|
|
|
endOfStream = nextData.endOfStream;
|
|
|
|
return true;
|
|
|
|
}
|
2015-01-23 20:32:17 +01:00
|
|
|
}
|
|
|
|
|
2018-01-24 13:52:10 +01:00
|
|
|
private void notifyLifecycleManagerOnError(ChannelFuture future, final ChannelHandlerContext ctx) {
|
|
|
|
future.addListener(new ChannelFutureListener() {
|
|
|
|
@Override
|
|
|
|
public void operationComplete(ChannelFuture future) throws Exception {
|
|
|
|
Throwable cause = future.cause();
|
|
|
|
if (cause != null) {
|
|
|
|
lifecycleManager.onError(ctx, true, cause);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-01-23 20:32:17 +01:00
|
|
|
/**
|
|
|
|
* Wrap headers so they can be written subject to flow-control. While headers do not have cost against the
|
|
|
|
* flow-control window their order with respect to other frames must be maintained, hence if a DATA frame is
|
|
|
|
* blocked on flow-control a HEADER frame must wait until this frame has been written.
|
|
|
|
*/
|
|
|
|
private final class FlowControlledHeaders extends FlowControlledBase {
|
|
|
|
private final Http2Headers headers;
|
|
|
|
private final int streamDependency;
|
|
|
|
private final short weight;
|
|
|
|
private final boolean exclusive;
|
|
|
|
|
2016-08-24 12:25:59 +02:00
|
|
|
FlowControlledHeaders(Http2Stream stream, Http2Headers headers, int streamDependency, short weight,
|
2015-06-30 19:10:17 +02:00
|
|
|
boolean exclusive, int padding, boolean endOfStream, ChannelPromise promise) {
|
|
|
|
super(stream, padding, endOfStream, promise);
|
2015-01-23 20:32:17 +01:00
|
|
|
this.headers = headers;
|
|
|
|
this.streamDependency = streamDependency;
|
|
|
|
this.weight = weight;
|
|
|
|
this.exclusive = exclusive;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int size() {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2015-06-30 19:10:17 +02:00
|
|
|
public void error(ChannelHandlerContext ctx, Throwable cause) {
|
|
|
|
if (ctx != null) {
|
2018-01-25 22:42:28 +01:00
|
|
|
lifecycleManager.onError(ctx, true, cause);
|
2015-06-30 19:10:17 +02:00
|
|
|
}
|
2015-01-23 20:32:17 +01:00
|
|
|
promise.tryFailure(cause);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2017-01-26 18:03:42 +01:00
|
|
|
public void write(ChannelHandlerContext ctx, int allowedBytes) {
|
2017-07-11 23:53:49 +02:00
|
|
|
boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream);
|
2015-06-04 20:55:18 +02:00
|
|
|
if (promise.isVoid()) {
|
|
|
|
promise = ctx.newPromise();
|
|
|
|
}
|
2015-06-30 21:31:08 +02:00
|
|
|
promise.addListener(this);
|
2015-06-30 19:10:17 +02:00
|
|
|
|
2017-01-26 18:03:42 +01:00
|
|
|
ChannelFuture f = frameWriter.writeHeaders(ctx, stream.id(), headers, streamDependency, weight, exclusive,
|
|
|
|
padding, endOfStream, promise);
|
|
|
|
// Writing headers may fail during the encode state if they violate HPACK limits.
|
|
|
|
Throwable failureCause = f.cause();
|
|
|
|
if (failureCause == null) {
|
2018-01-24 13:52:10 +01:00
|
|
|
// This just sets internal stream state which is used elsewhere in the codec and doesn't
|
|
|
|
// necessarily mean the write will complete successfully.
|
2017-07-11 23:53:49 +02:00
|
|
|
stream.headersSent(isInformational);
|
2017-01-26 18:03:42 +01:00
|
|
|
}
|
2015-01-23 20:32:17 +01:00
|
|
|
}
|
2015-06-04 20:55:18 +02:00
|
|
|
|
|
|
|
@Override
|
2015-06-30 19:10:17 +02:00
|
|
|
public boolean merge(ChannelHandlerContext ctx, Http2RemoteFlowController.FlowControlled next) {
|
2015-06-04 20:55:18 +02:00
|
|
|
return false;
|
|
|
|
}
|
2015-01-23 20:32:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Common base type for payloads to deliver via flow-control.
|
|
|
|
*/
|
|
|
|
public abstract class FlowControlledBase implements Http2RemoteFlowController.FlowControlled,
|
|
|
|
ChannelFutureListener {
|
|
|
|
protected final Http2Stream stream;
|
2015-06-04 20:55:18 +02:00
|
|
|
protected ChannelPromise promise;
|
|
|
|
protected boolean endOfStream;
|
2015-01-23 20:32:17 +01:00
|
|
|
protected int padding;
|
|
|
|
|
2017-01-24 21:50:39 +01:00
|
|
|
FlowControlledBase(final Http2Stream stream, int padding, boolean endOfStream,
|
2015-06-30 19:10:17 +02:00
|
|
|
final ChannelPromise promise) {
|
2015-01-23 20:32:17 +01:00
|
|
|
if (padding < 0) {
|
|
|
|
throw new IllegalArgumentException("padding must be >= 0");
|
|
|
|
}
|
|
|
|
this.padding = padding;
|
|
|
|
this.endOfStream = endOfStream;
|
|
|
|
this.stream = stream;
|
|
|
|
this.promise = promise;
|
|
|
|
}
|
|
|
|
|
2015-03-05 00:55:55 +01:00
|
|
|
@Override
|
|
|
|
public void writeComplete() {
|
|
|
|
if (endOfStream) {
|
2015-04-02 23:39:46 +02:00
|
|
|
lifecycleManager.closeStreamLocal(stream, promise);
|
2015-03-05 00:55:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-23 20:32:17 +01:00
|
|
|
@Override
|
|
|
|
public void operationComplete(ChannelFuture future) throws Exception {
|
|
|
|
if (!future.isSuccess()) {
|
2015-06-30 19:10:17 +02:00
|
|
|
error(flowController().channelHandlerContext(), future.cause());
|
2015-01-23 20:32:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|