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;
|
|
|
|
|
2015-02-04 14:00:43 +01:00
|
|
|
import static io.netty.buffer.Unpooled.directBuffer;
|
|
|
|
import static io.netty.buffer.Unpooled.unmodifiableBuffer;
|
|
|
|
import static io.netty.buffer.Unpooled.unreleasableBuffer;
|
2015-01-31 04:54:35 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.CONTINUATION_FRAME_HEADER_LENGTH;
|
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.DATA_FRAME_HEADER_LENGTH;
|
2015-01-20 01:48:11 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE;
|
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
|
2015-01-31 04:54:35 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.GO_AWAY_FRAME_HEADER_LENGTH;
|
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.HEADERS_FRAME_HEADER_LENGTH;
|
2015-01-20 01:48:11 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH;
|
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_BYTE;
|
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT;
|
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_WEIGHT;
|
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT;
|
2015-09-09 22:40:07 +02:00
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.PING_FRAME_PAYLOAD_LENGTH;
|
2015-01-20 01:48:11 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH;
|
2015-01-31 04:54:35 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_FRAME_LENGTH;
|
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.PUSH_PROMISE_FRAME_HEADER_LENGTH;
|
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.RST_STREAM_FRAME_LENGTH;
|
2015-01-20 01:48:11 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
|
2015-01-31 04:54:35 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.WINDOW_UPDATE_FRAME_LENGTH;
|
2015-01-20 01:48:11 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.isMaxFrameSizeValid;
|
2015-01-31 04:54:35 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.writeFrameHeaderInternal;
|
2015-01-20 01:48:11 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedInt;
|
|
|
|
import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedShort;
|
|
|
|
import static io.netty.handler.codec.http2.Http2Error.FRAME_SIZE_ERROR;
|
2015-01-31 04:54:35 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
2015-01-20 01:48:11 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION;
|
|
|
|
import static io.netty.handler.codec.http2.Http2FrameTypes.DATA;
|
|
|
|
import static io.netty.handler.codec.http2.Http2FrameTypes.GO_AWAY;
|
|
|
|
import static io.netty.handler.codec.http2.Http2FrameTypes.HEADERS;
|
|
|
|
import static io.netty.handler.codec.http2.Http2FrameTypes.PING;
|
|
|
|
import static io.netty.handler.codec.http2.Http2FrameTypes.PRIORITY;
|
|
|
|
import static io.netty.handler.codec.http2.Http2FrameTypes.PUSH_PROMISE;
|
|
|
|
import static io.netty.handler.codec.http2.Http2FrameTypes.RST_STREAM;
|
|
|
|
import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
|
|
|
|
import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE;
|
|
|
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
2015-11-19 05:46:11 +01:00
|
|
|
import static java.lang.Math.max;
|
|
|
|
import static java.lang.Math.min;
|
|
|
|
|
|
|
|
import io.netty.buffer.ByteBuf;
|
|
|
|
import io.netty.channel.ChannelFuture;
|
|
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
|
|
import io.netty.channel.ChannelPromise;
|
|
|
|
import io.netty.handler.codec.http2.Http2CodecUtil.SimpleChannelPromiseAggregator;
|
|
|
|
import io.netty.handler.codec.http2.Http2FrameWriter.Configuration;
|
2015-04-10 18:13:32 +02:00
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
/**
|
|
|
|
* A {@link Http2FrameWriter} that supports all frame types defined by the HTTP/2 specification.
|
|
|
|
*/
|
|
|
|
public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSizePolicy, Configuration {
|
|
|
|
private static final String STREAM_ID = "Stream ID";
|
|
|
|
private static final String STREAM_DEPENDENCY = "Stream Dependency";
|
2015-01-31 04:54:35 +01:00
|
|
|
/**
|
|
|
|
* This buffer is allocated to the maximum padding size needed, and filled with padding.
|
|
|
|
* When padding is needed it can be taken as a slice of this buffer. Users should call {@link ByteBuf#retain()}
|
|
|
|
* before using their slice.
|
|
|
|
*/
|
2015-02-04 14:00:43 +01:00
|
|
|
private static final ByteBuf ZERO_BUFFER = unmodifiableBuffer(
|
|
|
|
unreleasableBuffer(directBuffer(MAX_UNSIGNED_BYTE).writeZero(MAX_UNSIGNED_BYTE)));
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
private final Http2HeadersEncoder headersEncoder;
|
|
|
|
private int maxFrameSize;
|
|
|
|
|
|
|
|
public DefaultHttp2FrameWriter() {
|
|
|
|
this(new DefaultHttp2HeadersEncoder());
|
|
|
|
}
|
|
|
|
|
2015-11-24 04:14:21 +01:00
|
|
|
public DefaultHttp2FrameWriter(Http2HeadersEncoder.SensitivityDetector headersSensativityDetector) {
|
|
|
|
this(new DefaultHttp2HeadersEncoder(Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE, headersSensativityDetector));
|
|
|
|
}
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
public DefaultHttp2FrameWriter(Http2HeadersEncoder headersEncoder) {
|
|
|
|
this.headersEncoder = headersEncoder;
|
|
|
|
maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Configuration configuration() {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Http2HeaderTable headerTable() {
|
|
|
|
return headersEncoder.configuration().headerTable();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Http2FrameSizePolicy frameSizePolicy() {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void maxFrameSize(int max) throws Http2Exception {
|
|
|
|
if (!isMaxFrameSizeValid(max)) {
|
|
|
|
throw connectionError(FRAME_SIZE_ERROR, "Invalid MAX_FRAME_SIZE specified in sent settings: %d", max);
|
|
|
|
}
|
|
|
|
maxFrameSize = max;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int maxFrameSize() {
|
|
|
|
return maxFrameSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void close() { }
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
|
|
|
|
int padding, boolean endStream, ChannelPromise promise) {
|
2015-11-19 05:46:11 +01:00
|
|
|
final SimpleChannelPromiseAggregator promiseAggregator =
|
2015-01-31 04:54:35 +01:00
|
|
|
new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
|
2015-11-19 05:46:11 +01:00
|
|
|
final DataFrameHeader header = new DataFrameHeader(ctx, streamId);
|
|
|
|
boolean needToReleaseHeaders = true;
|
|
|
|
boolean needToReleaseData = true;
|
2015-01-20 01:48:11 +01:00
|
|
|
try {
|
|
|
|
verifyStreamId(streamId, STREAM_ID);
|
|
|
|
verifyPadding(padding);
|
|
|
|
|
2015-11-19 05:46:11 +01:00
|
|
|
boolean lastFrame;
|
|
|
|
int remainingData = data.readableBytes();
|
|
|
|
do {
|
|
|
|
// Determine how much data and padding to write in this frame. Put all padding at the end.
|
|
|
|
int frameDataBytes = min(remainingData, maxFrameSize);
|
|
|
|
int framePaddingBytes = min(padding, max(0, (maxFrameSize - 1) - frameDataBytes));
|
|
|
|
|
|
|
|
// Decrement the remaining counters.
|
|
|
|
padding -= framePaddingBytes;
|
|
|
|
remainingData -= frameDataBytes;
|
|
|
|
|
|
|
|
// Determine whether or not this is the last frame to be sent.
|
|
|
|
lastFrame = remainingData == 0 && padding == 0;
|
|
|
|
|
|
|
|
// Only the last frame is not retained. Until then, the outer finally must release.
|
|
|
|
ByteBuf frameHeader = header.slice(frameDataBytes, framePaddingBytes, lastFrame && endStream);
|
|
|
|
needToReleaseHeaders = !lastFrame;
|
|
|
|
ctx.write(lastFrame ? frameHeader : frameHeader.retain(), promiseAggregator.newPromise());
|
|
|
|
|
|
|
|
// Write the frame data.
|
|
|
|
ByteBuf frameData = data.readSlice(frameDataBytes);
|
|
|
|
// Only the last frame is not retained. Until then, the outer finally must release.
|
|
|
|
needToReleaseData = !lastFrame;
|
|
|
|
ctx.write(lastFrame ? frameData : frameData.retain(), promiseAggregator.newPromise());
|
|
|
|
|
|
|
|
// Write the frame padding.
|
|
|
|
if (framePaddingBytes > 0) {
|
|
|
|
ctx.write(ZERO_BUFFER.slice(0, framePaddingBytes), promiseAggregator.newPromise());
|
|
|
|
}
|
|
|
|
} while (!lastFrame);
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
return promiseAggregator.doneAllocatingPromises();
|
|
|
|
} catch (Throwable t) {
|
2015-11-19 05:46:11 +01:00
|
|
|
if (needToReleaseHeaders) {
|
|
|
|
header.release();
|
|
|
|
}
|
|
|
|
if (needToReleaseData) {
|
2015-01-31 04:54:35 +01:00
|
|
|
data.release();
|
|
|
|
}
|
|
|
|
return promiseAggregator.setFailure(t);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId,
|
|
|
|
Http2Headers headers, int padding, boolean endStream, ChannelPromise promise) {
|
2015-01-31 04:54:35 +01:00
|
|
|
return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
|
|
|
|
false, 0, (short) 0, false, promise);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId,
|
|
|
|
Http2Headers headers, int streamDependency, short weight, boolean exclusive,
|
|
|
|
int padding, boolean endStream, ChannelPromise promise) {
|
2015-01-31 04:54:35 +01:00
|
|
|
return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
|
|
|
|
true, streamDependency, weight, exclusive, 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) {
|
|
|
|
try {
|
|
|
|
verifyStreamId(streamId, STREAM_ID);
|
|
|
|
verifyStreamId(streamDependency, STREAM_DEPENDENCY);
|
|
|
|
verifyWeight(weight);
|
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
ByteBuf buf = ctx.alloc().buffer(PRIORITY_FRAME_LENGTH);
|
|
|
|
writeFrameHeaderInternal(buf, PRIORITY_ENTRY_LENGTH, PRIORITY, new Http2Flags(), streamId);
|
2015-01-20 01:48:11 +01:00
|
|
|
long word1 = exclusive ? 0x80000000L | streamDependency : streamDependency;
|
2015-01-31 04:54:35 +01:00
|
|
|
writeUnsignedInt(word1, buf);
|
2015-01-20 01:48:11 +01:00
|
|
|
// Adjust the weight so that it fits into a single byte on the wire.
|
2015-01-31 04:54:35 +01:00
|
|
|
buf.writeByte(weight - 1);
|
|
|
|
return ctx.write(buf, promise);
|
|
|
|
} catch (Throwable t) {
|
|
|
|
return promise.setFailure(t);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode,
|
|
|
|
ChannelPromise promise) {
|
|
|
|
try {
|
|
|
|
verifyStreamId(streamId, STREAM_ID);
|
|
|
|
verifyErrorCode(errorCode);
|
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
ByteBuf buf = ctx.alloc().buffer(RST_STREAM_FRAME_LENGTH);
|
|
|
|
writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, RST_STREAM, new Http2Flags(), streamId);
|
|
|
|
writeUnsignedInt(errorCode, buf);
|
|
|
|
return ctx.write(buf, promise);
|
|
|
|
} catch (Throwable t) {
|
|
|
|
return promise.setFailure(t);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeSettings(ChannelHandlerContext ctx, Http2Settings settings,
|
|
|
|
ChannelPromise promise) {
|
|
|
|
try {
|
|
|
|
checkNotNull(settings, "settings");
|
|
|
|
int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
|
2015-01-31 04:54:35 +01:00
|
|
|
ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH + settings.size() * SETTING_ENTRY_LENGTH);
|
|
|
|
writeFrameHeaderInternal(buf, payloadLength, SETTINGS, new Http2Flags(), 0);
|
2015-07-13 23:53:20 +02:00
|
|
|
for (Http2Settings.PrimitiveEntry<Long> entry : settings.entries()) {
|
2015-01-31 04:54:35 +01:00
|
|
|
writeUnsignedShort(entry.key(), buf);
|
|
|
|
writeUnsignedInt(entry.value(), buf);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
2015-01-31 04:54:35 +01:00
|
|
|
return ctx.write(buf, promise);
|
|
|
|
} catch (Throwable t) {
|
|
|
|
return promise.setFailure(t);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) {
|
|
|
|
try {
|
2015-01-31 04:54:35 +01:00
|
|
|
ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
|
|
|
|
writeFrameHeaderInternal(buf, 0, SETTINGS, new Http2Flags().ack(true), 0);
|
|
|
|
return ctx.write(buf, promise);
|
|
|
|
} catch (Throwable t) {
|
|
|
|
return promise.setFailure(t);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writePing(ChannelHandlerContext ctx, boolean ack, ByteBuf data,
|
|
|
|
ChannelPromise promise) {
|
2015-01-31 04:54:35 +01:00
|
|
|
boolean releaseData = true;
|
|
|
|
SimpleChannelPromiseAggregator promiseAggregator =
|
|
|
|
new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
|
2015-01-20 01:48:11 +01:00
|
|
|
try {
|
2015-09-09 22:40:07 +02:00
|
|
|
verifyPingPayload(data);
|
2015-01-20 01:48:11 +01:00
|
|
|
Http2Flags flags = ack ? new Http2Flags().ack(true) : new Http2Flags();
|
2015-01-31 04:54:35 +01:00
|
|
|
ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
|
|
|
|
writeFrameHeaderInternal(buf, data.readableBytes(), PING, flags, 0);
|
|
|
|
ctx.write(buf, promiseAggregator.newPromise());
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
// Write the debug data.
|
2015-01-31 04:54:35 +01:00
|
|
|
releaseData = false;
|
|
|
|
ctx.write(data, promiseAggregator.newPromise());
|
|
|
|
|
|
|
|
return promiseAggregator.doneAllocatingPromises();
|
|
|
|
} catch (Throwable t) {
|
|
|
|
if (releaseData) {
|
|
|
|
data.release();
|
|
|
|
}
|
|
|
|
return promiseAggregator.setFailure(t);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writePushPromise(ChannelHandlerContext ctx, int streamId,
|
|
|
|
int promisedStreamId, Http2Headers headers, int padding, ChannelPromise promise) {
|
|
|
|
ByteBuf headerBlock = null;
|
2015-01-31 04:54:35 +01:00
|
|
|
SimpleChannelPromiseAggregator promiseAggregator =
|
|
|
|
new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
|
2015-01-20 01:48:11 +01:00
|
|
|
try {
|
|
|
|
verifyStreamId(streamId, STREAM_ID);
|
|
|
|
verifyStreamId(promisedStreamId, "Promised Stream ID");
|
|
|
|
verifyPadding(padding);
|
|
|
|
|
|
|
|
// Encode the entire header block into an intermediate buffer.
|
|
|
|
headerBlock = ctx.alloc().buffer();
|
|
|
|
headersEncoder.encodeHeaders(headers, headerBlock);
|
|
|
|
|
|
|
|
// Read the first fragment (possibly everything).
|
|
|
|
Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
|
2015-01-31 04:54:35 +01:00
|
|
|
// INT_FIELD_LENGTH is for the length of the promisedStreamId
|
|
|
|
int nonFragmentLength = INT_FIELD_LENGTH + padding + flags.getPaddingPresenceFieldLength();
|
2015-01-20 01:48:11 +01:00
|
|
|
int maxFragmentLength = maxFrameSize - nonFragmentLength;
|
|
|
|
ByteBuf fragment =
|
2015-11-19 05:46:11 +01:00
|
|
|
headerBlock.readSlice(min(headerBlock.readableBytes(), maxFragmentLength)).retain();
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
flags.endOfHeaders(!headerBlock.isReadable());
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
int payloadLength = fragment.readableBytes() + nonFragmentLength;
|
2015-01-31 04:54:35 +01:00
|
|
|
ByteBuf buf = ctx.alloc().buffer(PUSH_PROMISE_FRAME_HEADER_LENGTH);
|
|
|
|
writeFrameHeaderInternal(buf, payloadLength, PUSH_PROMISE, flags, streamId);
|
|
|
|
writePaddingLength(buf, padding);
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
// Write out the promised stream ID.
|
2015-01-31 04:54:35 +01:00
|
|
|
buf.writeInt(promisedStreamId);
|
|
|
|
ctx.write(buf, promiseAggregator.newPromise());
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
// Write the first fragment.
|
2015-01-31 04:54:35 +01:00
|
|
|
ctx.write(fragment, promiseAggregator.newPromise());
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
if (padding > 0) { // Write out the padding, if any.
|
2015-11-19 05:46:11 +01:00
|
|
|
ctx.write(ZERO_BUFFER.slice(0, padding), promiseAggregator.newPromise());
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
if (!flags.endOfHeaders()) {
|
|
|
|
writeContinuationFrames(ctx, streamId, headerBlock, padding, promiseAggregator);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
return promiseAggregator.doneAllocatingPromises();
|
|
|
|
} catch (Throwable t) {
|
|
|
|
return promiseAggregator.setFailure(t);
|
2015-01-20 01:48:11 +01:00
|
|
|
} finally {
|
|
|
|
if (headerBlock != null) {
|
|
|
|
headerBlock.release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
|
|
|
|
ByteBuf debugData, ChannelPromise promise) {
|
2015-01-31 04:54:35 +01:00
|
|
|
boolean releaseData = true;
|
|
|
|
SimpleChannelPromiseAggregator promiseAggregator =
|
|
|
|
new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
|
2015-01-20 01:48:11 +01:00
|
|
|
try {
|
|
|
|
verifyStreamOrConnectionId(lastStreamId, "Last Stream ID");
|
|
|
|
verifyErrorCode(errorCode);
|
|
|
|
|
|
|
|
int payloadLength = 8 + debugData.readableBytes();
|
2015-01-31 04:54:35 +01:00
|
|
|
ByteBuf buf = ctx.alloc().buffer(GO_AWAY_FRAME_HEADER_LENGTH);
|
|
|
|
writeFrameHeaderInternal(buf, payloadLength, GO_AWAY, new Http2Flags(), 0);
|
|
|
|
buf.writeInt(lastStreamId);
|
|
|
|
writeUnsignedInt(errorCode, buf);
|
|
|
|
ctx.write(buf, promiseAggregator.newPromise());
|
|
|
|
|
|
|
|
releaseData = false;
|
|
|
|
ctx.write(debugData, promiseAggregator.newPromise());
|
|
|
|
return promiseAggregator.doneAllocatingPromises();
|
|
|
|
} catch (Throwable t) {
|
|
|
|
if (releaseData) {
|
|
|
|
debugData.release();
|
|
|
|
}
|
|
|
|
return promiseAggregator.setFailure(t);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeWindowUpdate(ChannelHandlerContext ctx, int streamId,
|
|
|
|
int windowSizeIncrement, ChannelPromise promise) {
|
|
|
|
try {
|
|
|
|
verifyStreamOrConnectionId(streamId, STREAM_ID);
|
|
|
|
verifyWindowSizeIncrement(windowSizeIncrement);
|
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
ByteBuf buf = ctx.alloc().buffer(WINDOW_UPDATE_FRAME_LENGTH);
|
|
|
|
writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, WINDOW_UPDATE, new Http2Flags(), streamId);
|
|
|
|
buf.writeInt(windowSizeIncrement);
|
|
|
|
return ctx.write(buf, promise);
|
|
|
|
} catch (Throwable t) {
|
|
|
|
return promise.setFailure(t);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelFuture writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
|
|
|
|
Http2Flags flags, ByteBuf payload, ChannelPromise promise) {
|
2015-01-31 04:54:35 +01:00
|
|
|
boolean releaseData = true;
|
|
|
|
SimpleChannelPromiseAggregator promiseAggregator =
|
|
|
|
new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
|
2015-01-20 01:48:11 +01:00
|
|
|
try {
|
|
|
|
verifyStreamOrConnectionId(streamId, STREAM_ID);
|
2015-01-31 04:54:35 +01:00
|
|
|
ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
|
|
|
|
writeFrameHeaderInternal(buf, payload.readableBytes(), frameType, flags, streamId);
|
|
|
|
ctx.write(buf, promiseAggregator.newPromise());
|
|
|
|
|
|
|
|
releaseData = false;
|
|
|
|
ctx.write(payload, promiseAggregator.newPromise());
|
|
|
|
return promiseAggregator.doneAllocatingPromises();
|
|
|
|
} catch (Throwable t) {
|
|
|
|
if (releaseData) {
|
|
|
|
payload.release();
|
|
|
|
}
|
|
|
|
return promiseAggregator.setFailure(t);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
private ChannelFuture writeHeadersInternal(ChannelHandlerContext ctx,
|
2015-01-20 01:48:11 +01:00
|
|
|
int streamId, Http2Headers headers, int padding, boolean endStream,
|
2015-01-31 04:54:35 +01:00
|
|
|
boolean hasPriority, int streamDependency, short weight, boolean exclusive, ChannelPromise promise) {
|
2015-01-20 01:48:11 +01:00
|
|
|
ByteBuf headerBlock = null;
|
2015-01-31 04:54:35 +01:00
|
|
|
SimpleChannelPromiseAggregator promiseAggregator =
|
|
|
|
new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
|
2015-01-20 01:48:11 +01:00
|
|
|
try {
|
|
|
|
verifyStreamId(streamId, STREAM_ID);
|
|
|
|
if (hasPriority) {
|
|
|
|
verifyStreamOrConnectionId(streamDependency, STREAM_DEPENDENCY);
|
|
|
|
verifyPadding(padding);
|
|
|
|
verifyWeight(weight);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Encode the entire header block.
|
|
|
|
headerBlock = ctx.alloc().buffer();
|
|
|
|
headersEncoder.encodeHeaders(headers, headerBlock);
|
|
|
|
|
|
|
|
Http2Flags flags =
|
|
|
|
new Http2Flags().endOfStream(endStream).priorityPresent(hasPriority).paddingPresent(padding > 0);
|
|
|
|
|
|
|
|
// Read the first fragment (possibly everything).
|
2015-01-31 04:54:35 +01:00
|
|
|
int nonFragmentBytes = padding + flags.getNumPriorityBytes() + flags.getPaddingPresenceFieldLength();
|
2015-01-20 01:48:11 +01:00
|
|
|
int maxFragmentLength = maxFrameSize - nonFragmentBytes;
|
|
|
|
ByteBuf fragment =
|
2015-11-19 05:46:11 +01:00
|
|
|
headerBlock.readSlice(min(headerBlock.readableBytes(), maxFragmentLength)).retain();
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
// Set the end of headers flag for the first frame.
|
2015-01-31 04:54:35 +01:00
|
|
|
flags.endOfHeaders(!headerBlock.isReadable());
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
int payloadLength = fragment.readableBytes() + nonFragmentBytes;
|
2015-01-31 04:54:35 +01:00
|
|
|
ByteBuf buf = ctx.alloc().buffer(HEADERS_FRAME_HEADER_LENGTH);
|
|
|
|
writeFrameHeaderInternal(buf, payloadLength, HEADERS, flags, streamId);
|
|
|
|
writePaddingLength(buf, padding);
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
if (hasPriority) {
|
|
|
|
long word1 = exclusive ? 0x80000000L | streamDependency : streamDependency;
|
2015-01-31 04:54:35 +01:00
|
|
|
writeUnsignedInt(word1, buf);
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
// Adjust the weight so that it fits into a single byte on the wire.
|
2015-01-31 04:54:35 +01:00
|
|
|
buf.writeByte(weight - 1);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
2015-01-31 04:54:35 +01:00
|
|
|
ctx.write(buf, promiseAggregator.newPromise());
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
// Write the first fragment.
|
2015-01-31 04:54:35 +01:00
|
|
|
ctx.write(fragment, promiseAggregator.newPromise());
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
if (padding > 0) { // Write out the padding, if any.
|
2015-11-19 05:46:11 +01:00
|
|
|
ctx.write(ZERO_BUFFER.slice(0, padding), promiseAggregator.newPromise());
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
if (!flags.endOfHeaders()) {
|
|
|
|
writeContinuationFrames(ctx, streamId, headerBlock, padding, promiseAggregator);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
return promiseAggregator.doneAllocatingPromises();
|
|
|
|
} catch (Throwable t) {
|
|
|
|
return promiseAggregator.setFailure(t);
|
2015-01-20 01:48:11 +01:00
|
|
|
} finally {
|
|
|
|
if (headerBlock != null) {
|
|
|
|
headerBlock.release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-01-31 04:54:35 +01:00
|
|
|
* Writes as many continuation frames as needed until {@code padding} and {@code headerBlock} are consumed.
|
2015-01-20 01:48:11 +01:00
|
|
|
*/
|
2015-01-31 04:54:35 +01:00
|
|
|
private ChannelFuture writeContinuationFrames(ChannelHandlerContext ctx, int streamId,
|
|
|
|
ByteBuf headerBlock, int padding, SimpleChannelPromiseAggregator promiseAggregator) {
|
2015-01-20 01:48:11 +01:00
|
|
|
Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
|
|
|
|
int nonFragmentLength = padding + flags.getPaddingPresenceFieldLength();
|
|
|
|
int maxFragmentLength = maxFrameSize - nonFragmentLength;
|
2015-01-31 04:54:35 +01:00
|
|
|
// TODO: same padding is applied to all frames, is this desired?
|
|
|
|
if (maxFragmentLength <= 0) {
|
|
|
|
return promiseAggregator.setFailure(new IllegalArgumentException(
|
|
|
|
"Padding [" + padding + "] is too large for max frame size [" + maxFrameSize + "]"));
|
|
|
|
}
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
if (headerBlock.isReadable()) {
|
|
|
|
// The frame header (and padding) only changes on the last frame, so allocate it once and re-use
|
2015-11-19 05:46:11 +01:00
|
|
|
int fragmentReadableBytes = min(headerBlock.readableBytes(), maxFragmentLength);
|
2015-01-31 04:54:35 +01:00
|
|
|
int payloadLength = fragmentReadableBytes + nonFragmentLength;
|
|
|
|
ByteBuf buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH);
|
|
|
|
writeFrameHeaderInternal(buf, payloadLength, CONTINUATION, flags, streamId);
|
|
|
|
writePaddingLength(buf, padding);
|
|
|
|
|
|
|
|
do {
|
2015-11-19 05:46:11 +01:00
|
|
|
fragmentReadableBytes = min(headerBlock.readableBytes(), maxFragmentLength);
|
2015-01-31 04:54:35 +01:00
|
|
|
ByteBuf fragment = headerBlock.readSlice(fragmentReadableBytes).retain();
|
|
|
|
|
|
|
|
payloadLength = fragmentReadableBytes + nonFragmentLength;
|
|
|
|
if (headerBlock.isReadable()) {
|
|
|
|
ctx.write(buf.retain(), promiseAggregator.newPromise());
|
|
|
|
} else {
|
|
|
|
// The frame header is different for the last frame, so re-allocate and release the old buffer
|
|
|
|
flags = flags.endOfHeaders(true);
|
|
|
|
buf.release();
|
|
|
|
buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH);
|
|
|
|
writeFrameHeaderInternal(buf, payloadLength, CONTINUATION, flags, streamId);
|
|
|
|
writePaddingLength(buf, padding);
|
|
|
|
ctx.write(buf, promiseAggregator.newPromise());
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.write(fragment, promiseAggregator.newPromise());
|
|
|
|
|
|
|
|
// Write out the padding, if any.
|
2015-11-19 05:46:11 +01:00
|
|
|
if (padding > 0) {
|
|
|
|
ctx.write(ZERO_BUFFER.slice(0, padding), promiseAggregator.newPromise());
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
|
|
|
} while(headerBlock.isReadable());
|
|
|
|
}
|
|
|
|
return promiseAggregator;
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
private static void writePaddingLength(ByteBuf buf, int paddingLength) {
|
2015-01-20 01:48:11 +01:00
|
|
|
if (paddingLength > 0) {
|
2015-01-31 04:54:35 +01:00
|
|
|
// It is assumed that the padding length has been bounds checked before this
|
|
|
|
buf.writeByte(paddingLength);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void verifyStreamId(int streamId, String argumentName) {
|
|
|
|
if (streamId <= 0) {
|
|
|
|
throw new IllegalArgumentException(argumentName + " must be > 0");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void verifyStreamOrConnectionId(int streamId, String argumentName) {
|
|
|
|
if (streamId < 0) {
|
|
|
|
throw new IllegalArgumentException(argumentName + " must be >= 0");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void verifyPadding(int padding) {
|
|
|
|
if (padding < 0 || padding > MAX_UNSIGNED_BYTE) {
|
|
|
|
throw new IllegalArgumentException("Invalid padding value: " + padding);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void verifyWeight(short weight) {
|
|
|
|
if (weight < MIN_WEIGHT || weight > MAX_WEIGHT) {
|
|
|
|
throw new IllegalArgumentException("Invalid weight: " + weight);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void verifyErrorCode(long errorCode) {
|
|
|
|
if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) {
|
|
|
|
throw new IllegalArgumentException("Invalid errorCode: " + errorCode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void verifyWindowSizeIncrement(int windowSizeIncrement) {
|
|
|
|
if (windowSizeIncrement < 0) {
|
|
|
|
throw new IllegalArgumentException("WindowSizeIncrement must be >= 0");
|
|
|
|
}
|
|
|
|
}
|
2015-09-09 22:40:07 +02:00
|
|
|
|
|
|
|
private static void verifyPingPayload(ByteBuf data) {
|
|
|
|
if (data == null || data.readableBytes() != PING_FRAME_PAYLOAD_LENGTH) {
|
|
|
|
throw new IllegalArgumentException("Opaque data must be " + PING_FRAME_PAYLOAD_LENGTH + " bytes");
|
|
|
|
}
|
|
|
|
}
|
2015-11-19 05:46:11 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Utility class that manages the creation of frame header buffers for {@code DATA} frames. Attempts
|
|
|
|
* to reuse the same buffer repeatedly when splitting data into multiple frames.
|
|
|
|
*/
|
|
|
|
private static final class DataFrameHeader {
|
|
|
|
private final int streamId;
|
|
|
|
private final ByteBuf buffer;
|
|
|
|
private final Http2Flags flags = new Http2Flags();
|
|
|
|
private int prevData;
|
|
|
|
private int prevPadding;
|
|
|
|
private ByteBuf frameHeader;
|
|
|
|
|
|
|
|
DataFrameHeader(ChannelHandlerContext ctx, int streamId) {
|
|
|
|
// All padding will be put at the end, so in the worst case we need 3 headers:
|
|
|
|
// a repeated no-padding frame of maxFrameSize, a frame that has part data and part
|
|
|
|
// padding, and a frame that has the remainder of the padding.
|
|
|
|
buffer = ctx.alloc().buffer(3 * DATA_FRAME_HEADER_LENGTH);
|
|
|
|
this.streamId = streamId;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the frame header buffer configured for the current frame.
|
|
|
|
*/
|
|
|
|
ByteBuf slice(int data, int padding, boolean endOfStream) {
|
|
|
|
// Since we're reusing the current frame header whenever possible, check if anything changed
|
|
|
|
// that requires a new header.
|
|
|
|
if (data != prevData || padding != prevPadding
|
|
|
|
|| endOfStream != flags.endOfStream() || frameHeader == null) {
|
|
|
|
// Update the header state.
|
|
|
|
prevData = data;
|
|
|
|
prevPadding = padding;
|
|
|
|
flags.paddingPresent(padding > 0);
|
|
|
|
flags.endOfStream(endOfStream);
|
|
|
|
frameHeader = buffer.readSlice(DATA_FRAME_HEADER_LENGTH).writerIndex(0);
|
|
|
|
|
|
|
|
int payloadLength = data + padding + flags.getPaddingPresenceFieldLength();
|
|
|
|
writeFrameHeaderInternal(frameHeader, payloadLength, DATA, flags, streamId);
|
|
|
|
writePaddingLength(frameHeader, padding);
|
|
|
|
}
|
|
|
|
return frameHeader.slice();
|
|
|
|
}
|
|
|
|
|
|
|
|
void release() {
|
|
|
|
buffer.release();
|
|
|
|
}
|
|
|
|
}
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|