Replace 2 spaces with 4 spaces
This commit is contained in:
parent
10aee0c2be
commit
b92b65e557
@ -19,27 +19,27 @@ package io.netty.handler.codec.http2.draft10;
|
|||||||
* All error codes identified by the HTTP2 spec.
|
* All error codes identified by the HTTP2 spec.
|
||||||
*/
|
*/
|
||||||
public enum Http2Error {
|
public enum Http2Error {
|
||||||
NO_ERROR(0),
|
NO_ERROR(0),
|
||||||
PROTOCOL_ERROR(1),
|
PROTOCOL_ERROR(1),
|
||||||
INTERNAL_ERROR(2),
|
INTERNAL_ERROR(2),
|
||||||
FLOW_CONTROL_ERROR(3),
|
FLOW_CONTROL_ERROR(3),
|
||||||
SETTINGS_TIMEOUT(4),
|
SETTINGS_TIMEOUT(4),
|
||||||
STREAM_CLOSED(5),
|
STREAM_CLOSED(5),
|
||||||
FRAME_SIZE_ERROR(6),
|
FRAME_SIZE_ERROR(6),
|
||||||
REFUSED_STREAM(7),
|
REFUSED_STREAM(7),
|
||||||
CANCEL(8),
|
CANCEL(8),
|
||||||
COMPRESSION_ERROR(9),
|
COMPRESSION_ERROR(9),
|
||||||
CONNECT_ERROR(10),
|
CONNECT_ERROR(10),
|
||||||
ENHANCE_YOUR_CALM(11),
|
ENHANCE_YOUR_CALM(11),
|
||||||
INADEQUATE_SECURITY(12);
|
INADEQUATE_SECURITY(12);
|
||||||
|
|
||||||
private final int code;
|
private final int code;
|
||||||
|
|
||||||
private Http2Error(int code) {
|
private Http2Error(int code) {
|
||||||
this.code = code;
|
this.code = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCode() {
|
public int getCode() {
|
||||||
return this.code;
|
return this.code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,32 +19,32 @@ package io.netty.handler.codec.http2.draft10;
|
|||||||
* Exception thrown when an HTTP2 error was encountered.
|
* Exception thrown when an HTTP2 error was encountered.
|
||||||
*/
|
*/
|
||||||
public class Http2Exception extends Exception {
|
public class Http2Exception extends Exception {
|
||||||
private static final long serialVersionUID = -2292608019080068769L;
|
private static final long serialVersionUID = -2292608019080068769L;
|
||||||
|
|
||||||
private final Http2Error error;
|
private final Http2Error error;
|
||||||
|
|
||||||
public Http2Exception(Http2Error error) {
|
public Http2Exception(Http2Error error) {
|
||||||
this.error = error;
|
this.error = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Http2Exception(Http2Error error, String message) {
|
public Http2Exception(Http2Error error, String message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.error = error;
|
this.error = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Http2Error getError() {
|
public Http2Error getError() {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Http2Exception format(Http2Error error, String fmt, Object... args) {
|
public static Http2Exception format(Http2Error error, String fmt, Object... args) {
|
||||||
return new Http2Exception(error, String.format(fmt, args));
|
return new Http2Exception(error, String.format(fmt, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Http2Exception protocolError(String fmt, Object... args) {
|
public static Http2Exception protocolError(String fmt, Object... args) {
|
||||||
return format(Http2Error.PROTOCOL_ERROR, fmt, args);
|
return format(Http2Error.PROTOCOL_ERROR, fmt, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Http2Exception flowControlError(String fmt, Object... args) {
|
public static Http2Exception flowControlError(String fmt, Object... args) {
|
||||||
return format(Http2Error.FLOW_CONTROL_ERROR, fmt, args);
|
return format(Http2Error.FLOW_CONTROL_ERROR, fmt, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,20 +17,20 @@ package io.netty.handler.codec.http2.draft10;
|
|||||||
|
|
||||||
public class Http2StreamException extends Http2Exception {
|
public class Http2StreamException extends Http2Exception {
|
||||||
|
|
||||||
private static final long serialVersionUID = -7658235659648480024L;
|
private static final long serialVersionUID = -7658235659648480024L;
|
||||||
private final int streamId;
|
private final int streamId;
|
||||||
|
|
||||||
public Http2StreamException(int streamId, Http2Error error, String message) {
|
public Http2StreamException(int streamId, Http2Error error, String message) {
|
||||||
super(error, message);
|
super(error, message);
|
||||||
this.streamId = streamId;
|
this.streamId = streamId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Http2StreamException(int streamId, Http2Error error) {
|
public Http2StreamException(int streamId, Http2Error error) {
|
||||||
super(error);
|
super(error);
|
||||||
this.streamId = streamId;
|
this.streamId = streamId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getStreamId() {
|
public int getStreamId() {
|
||||||
return streamId;
|
return streamId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
|
|||||||
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
||||||
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.toByteBuf;
|
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.toByteBuf;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.DEFAULT_STREAM_PRIORITY;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.DEFAULT_STREAM_PRIORITY;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelFuture;
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelFutureListener;
|
import io.netty.channel.ChannelFutureListener;
|
||||||
@ -45,412 +46,412 @@ import com.google.common.collect.TreeMultiset;
|
|||||||
|
|
||||||
public class DefaultHttp2Connection implements Http2Connection {
|
public class DefaultHttp2Connection implements Http2Connection {
|
||||||
|
|
||||||
private final List<Listener> listeners = Lists.newArrayList();
|
private final List<Listener> listeners = Lists.newArrayList();
|
||||||
private final Map<Integer, Http2Stream> streamMap = Maps.newHashMap();
|
private final Map<Integer, Http2Stream> streamMap = Maps.newHashMap();
|
||||||
private final Multiset<Http2Stream> activeStreams = TreeMultiset.create();
|
private final Multiset<Http2Stream> activeStreams = TreeMultiset.create();
|
||||||
private final DefaultEndpoint localEndpoint;
|
private final DefaultEndpoint localEndpoint;
|
||||||
private final DefaultEndpoint remoteEndpoint;
|
private final DefaultEndpoint remoteEndpoint;
|
||||||
private boolean goAwaySent;
|
private boolean goAwaySent;
|
||||||
private boolean goAwayReceived;
|
private boolean goAwayReceived;
|
||||||
private ChannelFutureListener closeListener;
|
private ChannelFutureListener closeListener;
|
||||||
|
|
||||||
public DefaultHttp2Connection(boolean server) {
|
public DefaultHttp2Connection(boolean server) {
|
||||||
this.localEndpoint = new DefaultEndpoint(server);
|
this.localEndpoint = new DefaultEndpoint(server);
|
||||||
this.remoteEndpoint = new DefaultEndpoint(!server);
|
this.remoteEndpoint = new DefaultEndpoint(!server);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addListener(Listener listener) {
|
|
||||||
listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeListener(Listener listener) {
|
|
||||||
listeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Http2Stream getStreamOrFail(int streamId) throws Http2Exception {
|
|
||||||
Http2Stream stream = getStream(streamId);
|
|
||||||
if (stream == null) {
|
|
||||||
throw protocolError("Stream does not exist %d", streamId);
|
|
||||||
}
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Http2Stream getStream(int streamId) {
|
|
||||||
return streamMap.get(streamId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Http2Stream> getActiveStreams() {
|
|
||||||
// Copy the list in case any operation on the returned streams causes the activeStreams set
|
|
||||||
// to change.
|
|
||||||
return ImmutableList.copyOf(activeStreams);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Endpoint local() {
|
|
||||||
return localEndpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Endpoint remote() {
|
|
||||||
return remoteEndpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendGoAway(ChannelHandlerContext ctx, ChannelPromise promise, Http2Exception cause) {
|
|
||||||
closeListener = getOrCreateCloseListener(ctx, promise);
|
|
||||||
ChannelFuture future;
|
|
||||||
if (!goAwaySent) {
|
|
||||||
goAwaySent = true;
|
|
||||||
|
|
||||||
int errorCode = cause != null ? cause.getError().getCode() : NO_ERROR.getCode();
|
|
||||||
ByteBuf debugData = toByteBuf(ctx, cause);
|
|
||||||
|
|
||||||
Http2GoAwayFrame frame = new DefaultHttp2GoAwayFrame.Builder().setErrorCode(errorCode)
|
|
||||||
.setLastStreamId(remote().getLastStreamCreated()).setDebugData(debugData).build();
|
|
||||||
future = ctx.writeAndFlush(frame);
|
|
||||||
} else {
|
|
||||||
future = ctx.newSucceededFuture();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are no active streams, close immediately after the send is complete.
|
@Override
|
||||||
// Otherwise wait until all streams are inactive.
|
public void addListener(Listener listener) {
|
||||||
if (cause != null || activeStreams.isEmpty()) {
|
listeners.add(listener);
|
||||||
future.addListener(closeListener);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void goAwayReceived() {
|
public void removeListener(Listener listener) {
|
||||||
goAwayReceived = true;
|
listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isGoAwaySent() {
|
public Http2Stream getStreamOrFail(int streamId) throws Http2Exception {
|
||||||
return goAwaySent;
|
Http2Stream stream = getStream(streamId);
|
||||||
}
|
if (stream == null) {
|
||||||
|
throw protocolError("Stream does not exist %d", streamId);
|
||||||
@Override
|
|
||||||
public boolean isGoAwayReceived() {
|
|
||||||
return goAwayReceived;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isGoAway() {
|
|
||||||
return isGoAwaySent() || isGoAwayReceived();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ChannelFutureListener getOrCreateCloseListener(final ChannelHandlerContext ctx,
|
|
||||||
final ChannelPromise promise) {
|
|
||||||
if (closeListener == null) {
|
|
||||||
closeListener = new ChannelFutureListener() {
|
|
||||||
@Override
|
|
||||||
public void operationComplete(ChannelFuture future) throws Exception {
|
|
||||||
ctx.close(promise);
|
|
||||||
}
|
}
|
||||||
};
|
return stream;
|
||||||
}
|
|
||||||
return closeListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyStreamClosed(int id) {
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.streamClosed(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyStreamCreated(int id) {
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.streamCreated(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple stream implementation. Streams can be compared to each other by priority.
|
|
||||||
*/
|
|
||||||
private class DefaultStream implements Http2Stream {
|
|
||||||
private final int id;
|
|
||||||
private State state = State.IDLE;
|
|
||||||
private int priority;
|
|
||||||
|
|
||||||
public DefaultStream(int id) {
|
|
||||||
this.id = id;
|
|
||||||
this.priority = DEFAULT_STREAM_PRIORITY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getId() {
|
public Http2Stream getStream(int streamId) {
|
||||||
return id;
|
return streamMap.get(streamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public State getState() {
|
public List<Http2Stream> getActiveStreams() {
|
||||||
return state;
|
// Copy the list in case any operation on the returned streams causes the activeStreams set
|
||||||
|
// to change.
|
||||||
|
return ImmutableList.copyOf(activeStreams);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Http2Stream other) {
|
public Endpoint local() {
|
||||||
// Sort streams with the same priority by their ID.
|
return localEndpoint;
|
||||||
if (priority == other.getPriority()) {
|
|
||||||
return id - other.getId();
|
|
||||||
}
|
|
||||||
return priority - other.getPriority();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verifyState(Http2Error error, State... allowedStates) throws Http2Exception {
|
public Endpoint remote() {
|
||||||
Predicate<State> predicate = Predicates.in(Arrays.asList(allowedStates));
|
return remoteEndpoint;
|
||||||
if (!predicate.apply(state)) {
|
|
||||||
throw format(error, "Stream %d in unexpected state: %s", id, state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPriority(int priority) throws Http2Exception {
|
public void sendGoAway(ChannelHandlerContext ctx, ChannelPromise promise, Http2Exception cause) {
|
||||||
if (priority < 0) {
|
closeListener = getOrCreateCloseListener(ctx, promise);
|
||||||
throw protocolError("Invalid priority: %d", priority);
|
ChannelFuture future;
|
||||||
}
|
if (!goAwaySent) {
|
||||||
|
goAwaySent = true;
|
||||||
|
|
||||||
// If it was active, we must remove it from the set before changing the priority.
|
int errorCode = cause != null ? cause.getError().getCode() : NO_ERROR.getCode();
|
||||||
// Otherwise it won't be able to locate the stream in the set.
|
ByteBuf debugData = toByteBuf(ctx, cause);
|
||||||
boolean wasActive = activeStreams.remove(this);
|
|
||||||
this.priority = priority;
|
|
||||||
|
|
||||||
// If this stream was in the active set, re-add it so that it's properly sorted.
|
Http2GoAwayFrame frame = new DefaultHttp2GoAwayFrame.Builder().setErrorCode(errorCode)
|
||||||
if (wasActive) {
|
.setLastStreamId(remote().getLastStreamCreated()).setDebugData(debugData).build();
|
||||||
activeStreams.add(this);
|
future = ctx.writeAndFlush(frame);
|
||||||
}
|
} else {
|
||||||
|
future = ctx.newSucceededFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no active streams, close immediately after the send is complete.
|
||||||
|
// Otherwise wait until all streams are inactive.
|
||||||
|
if (cause != null || activeStreams.isEmpty()) {
|
||||||
|
future.addListener(closeListener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPriority() {
|
public void goAwayReceived() {
|
||||||
return priority;
|
goAwayReceived = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void openForPush() throws Http2Exception {
|
public boolean isGoAwaySent() {
|
||||||
switch (state) {
|
return goAwaySent;
|
||||||
case RESERVED_LOCAL:
|
|
||||||
state = State.HALF_CLOSED_REMOTE;
|
|
||||||
break;
|
|
||||||
case RESERVED_REMOTE:
|
|
||||||
state = State.HALF_CLOSED_LOCAL;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw protocolError("Attempting to open non-reserved stream for push");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close(ChannelHandlerContext ctx, ChannelFuture future) {
|
public boolean isGoAwayReceived() {
|
||||||
if (state == State.CLOSED) {
|
return goAwayReceived;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = State.CLOSED;
|
|
||||||
activeStreams.remove(this);
|
|
||||||
streamMap.remove(id);
|
|
||||||
notifyStreamClosed(id);
|
|
||||||
|
|
||||||
// If this connection is closing and there are no longer any
|
|
||||||
// active streams, close after the current operation completes.
|
|
||||||
if (closeListener != null && activeStreams.isEmpty()) {
|
|
||||||
future.addListener(closeListener);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void closeLocalSide(ChannelHandlerContext ctx, ChannelFuture future) {
|
public boolean isGoAway() {
|
||||||
switch (state) {
|
return isGoAwaySent() || isGoAwayReceived();
|
||||||
case OPEN:
|
|
||||||
case HALF_CLOSED_LOCAL:
|
|
||||||
state = State.HALF_CLOSED_LOCAL;
|
|
||||||
break;
|
|
||||||
case HALF_CLOSED_REMOTE:
|
|
||||||
case RESERVED_LOCAL:
|
|
||||||
case RESERVED_REMOTE:
|
|
||||||
case IDLE:
|
|
||||||
case CLOSED:
|
|
||||||
default:
|
|
||||||
close(ctx, future);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private ChannelFutureListener getOrCreateCloseListener(final ChannelHandlerContext ctx,
|
||||||
public void closeRemoteSide(ChannelHandlerContext ctx, ChannelFuture future) {
|
final ChannelPromise promise) {
|
||||||
switch (state) {
|
if (closeListener == null) {
|
||||||
case OPEN:
|
closeListener = new ChannelFutureListener() {
|
||||||
case HALF_CLOSED_REMOTE:
|
@Override
|
||||||
state = State.HALF_CLOSED_REMOTE;
|
public void operationComplete(ChannelFuture future) throws Exception {
|
||||||
break;
|
ctx.close(promise);
|
||||||
case RESERVED_LOCAL:
|
}
|
||||||
case RESERVED_REMOTE:
|
};
|
||||||
case IDLE:
|
}
|
||||||
case HALF_CLOSED_LOCAL:
|
return closeListener;
|
||||||
case CLOSED:
|
|
||||||
default:
|
|
||||||
close(ctx, future);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void notifyStreamClosed(int id) {
|
||||||
public boolean isRemoteSideOpen() {
|
for (Listener listener : listeners) {
|
||||||
switch (state) {
|
listener.streamClosed(id);
|
||||||
case HALF_CLOSED_LOCAL:
|
}
|
||||||
case OPEN:
|
|
||||||
case RESERVED_REMOTE:
|
|
||||||
return true;
|
|
||||||
case IDLE:
|
|
||||||
case RESERVED_LOCAL:
|
|
||||||
case HALF_CLOSED_REMOTE:
|
|
||||||
case CLOSED:
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void notifyStreamCreated(int id) {
|
||||||
public boolean isLocalSideOpen() {
|
for (Listener listener : listeners) {
|
||||||
switch (state) {
|
listener.streamCreated(id);
|
||||||
case HALF_CLOSED_REMOTE:
|
}
|
||||||
case OPEN:
|
|
||||||
case RESERVED_LOCAL:
|
|
||||||
return true;
|
|
||||||
case IDLE:
|
|
||||||
case RESERVED_REMOTE:
|
|
||||||
case HALF_CLOSED_LOCAL:
|
|
||||||
case CLOSED:
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple endpoint implementation.
|
|
||||||
*/
|
|
||||||
private class DefaultEndpoint implements Endpoint {
|
|
||||||
private int nextStreamId;
|
|
||||||
private int lastStreamCreated;
|
|
||||||
private int maxStreams = Integer.MAX_VALUE;
|
|
||||||
private boolean pushToAllowed = true;
|
|
||||||
|
|
||||||
public DefaultEndpoint(boolean serverEndpoint) {
|
|
||||||
// Determine the starting stream ID for this endpoint. Zero is reserved for the
|
|
||||||
// connection and 1 is reserved for responding to an upgrade from HTTP 1.1.
|
|
||||||
// Client-initiated streams use odd identifiers and server-initiated streams use
|
|
||||||
// even.
|
|
||||||
nextStreamId = serverEndpoint ? 2 : 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public DefaultStream createStream(int streamId, int priority, boolean halfClosed)
|
* Simple stream implementation. Streams can be compared to each other by priority.
|
||||||
throws Http2Exception {
|
*/
|
||||||
checkNewStreamAllowed(streamId);
|
private class DefaultStream implements Http2Stream {
|
||||||
|
private final int id;
|
||||||
|
private State state = State.IDLE;
|
||||||
|
private int priority;
|
||||||
|
|
||||||
// Create and initialize the stream.
|
public DefaultStream(int id) {
|
||||||
DefaultStream stream = new DefaultStream(streamId);
|
this.id = id;
|
||||||
stream.setPriority(priority);
|
this.priority = DEFAULT_STREAM_PRIORITY;
|
||||||
if (halfClosed) {
|
}
|
||||||
stream.state = isLocal() ? State.HALF_CLOSED_LOCAL : State.HALF_CLOSED_REMOTE;
|
|
||||||
} else {
|
|
||||||
stream.state = State.OPEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the next and last stream IDs.
|
@Override
|
||||||
nextStreamId += 2;
|
public int getId() {
|
||||||
lastStreamCreated = streamId;
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
// Register the stream and mark it as active.
|
@Override
|
||||||
streamMap.put(streamId, stream);
|
public State getState() {
|
||||||
activeStreams.add(stream);
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
notifyStreamCreated(streamId);
|
@Override
|
||||||
return stream;
|
public int compareTo(Http2Stream other) {
|
||||||
|
// Sort streams with the same priority by their ID.
|
||||||
|
if (priority == other.getPriority()) {
|
||||||
|
return id - other.getId();
|
||||||
|
}
|
||||||
|
return priority - other.getPriority();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verifyState(Http2Error error, State... allowedStates) throws Http2Exception {
|
||||||
|
Predicate<State> predicate = Predicates.in(Arrays.asList(allowedStates));
|
||||||
|
if (!predicate.apply(state)) {
|
||||||
|
throw format(error, "Stream %d in unexpected state: %s", id, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPriority(int priority) throws Http2Exception {
|
||||||
|
if (priority < 0) {
|
||||||
|
throw protocolError("Invalid priority: %d", priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it was active, we must remove it from the set before changing the priority.
|
||||||
|
// Otherwise it won't be able to locate the stream in the set.
|
||||||
|
boolean wasActive = activeStreams.remove(this);
|
||||||
|
this.priority = priority;
|
||||||
|
|
||||||
|
// If this stream was in the active set, re-add it so that it's properly sorted.
|
||||||
|
if (wasActive) {
|
||||||
|
activeStreams.add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void openForPush() throws Http2Exception {
|
||||||
|
switch (state) {
|
||||||
|
case RESERVED_LOCAL:
|
||||||
|
state = State.HALF_CLOSED_REMOTE;
|
||||||
|
break;
|
||||||
|
case RESERVED_REMOTE:
|
||||||
|
state = State.HALF_CLOSED_LOCAL;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw protocolError("Attempting to open non-reserved stream for push");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close(ChannelHandlerContext ctx, ChannelFuture future) {
|
||||||
|
if (state == State.CLOSED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = State.CLOSED;
|
||||||
|
activeStreams.remove(this);
|
||||||
|
streamMap.remove(id);
|
||||||
|
notifyStreamClosed(id);
|
||||||
|
|
||||||
|
// If this connection is closing and there are no longer any
|
||||||
|
// active streams, close after the current operation completes.
|
||||||
|
if (closeListener != null && activeStreams.isEmpty()) {
|
||||||
|
future.addListener(closeListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeLocalSide(ChannelHandlerContext ctx, ChannelFuture future) {
|
||||||
|
switch (state) {
|
||||||
|
case OPEN:
|
||||||
|
case HALF_CLOSED_LOCAL:
|
||||||
|
state = State.HALF_CLOSED_LOCAL;
|
||||||
|
break;
|
||||||
|
case HALF_CLOSED_REMOTE:
|
||||||
|
case RESERVED_LOCAL:
|
||||||
|
case RESERVED_REMOTE:
|
||||||
|
case IDLE:
|
||||||
|
case CLOSED:
|
||||||
|
default:
|
||||||
|
close(ctx, future);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeRemoteSide(ChannelHandlerContext ctx, ChannelFuture future) {
|
||||||
|
switch (state) {
|
||||||
|
case OPEN:
|
||||||
|
case HALF_CLOSED_REMOTE:
|
||||||
|
state = State.HALF_CLOSED_REMOTE;
|
||||||
|
break;
|
||||||
|
case RESERVED_LOCAL:
|
||||||
|
case RESERVED_REMOTE:
|
||||||
|
case IDLE:
|
||||||
|
case HALF_CLOSED_LOCAL:
|
||||||
|
case CLOSED:
|
||||||
|
default:
|
||||||
|
close(ctx, future);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRemoteSideOpen() {
|
||||||
|
switch (state) {
|
||||||
|
case HALF_CLOSED_LOCAL:
|
||||||
|
case OPEN:
|
||||||
|
case RESERVED_REMOTE:
|
||||||
|
return true;
|
||||||
|
case IDLE:
|
||||||
|
case RESERVED_LOCAL:
|
||||||
|
case HALF_CLOSED_REMOTE:
|
||||||
|
case CLOSED:
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocalSideOpen() {
|
||||||
|
switch (state) {
|
||||||
|
case HALF_CLOSED_REMOTE:
|
||||||
|
case OPEN:
|
||||||
|
case RESERVED_LOCAL:
|
||||||
|
return true;
|
||||||
|
case IDLE:
|
||||||
|
case RESERVED_REMOTE:
|
||||||
|
case HALF_CLOSED_LOCAL:
|
||||||
|
case CLOSED:
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public DefaultStream reservePushStream(int streamId, Http2Stream parent) throws Http2Exception {
|
* Simple endpoint implementation.
|
||||||
if (parent == null) {
|
*/
|
||||||
throw protocolError("Parent stream missing");
|
private class DefaultEndpoint implements Endpoint {
|
||||||
}
|
private int nextStreamId;
|
||||||
if (isLocal() ? !parent.isLocalSideOpen() : !parent.isRemoteSideOpen()) {
|
private int lastStreamCreated;
|
||||||
throw protocolError("Stream %d is not open for sending push promise", parent.getId());
|
private int maxStreams = Integer.MAX_VALUE;
|
||||||
}
|
private boolean pushToAllowed = true;
|
||||||
if (!opposite().isPushToAllowed()) {
|
|
||||||
throw protocolError("Server push not allowed to opposite endpoint.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and initialize the stream.
|
public DefaultEndpoint(boolean serverEndpoint) {
|
||||||
DefaultStream stream = new DefaultStream(streamId);
|
// Determine the starting stream ID for this endpoint. Zero is reserved for the
|
||||||
stream.setPriority(parent.getPriority() + 1);
|
// connection and 1 is reserved for responding to an upgrade from HTTP 1.1.
|
||||||
stream.state = isLocal() ? State.RESERVED_LOCAL : State.RESERVED_REMOTE;
|
// Client-initiated streams use odd identifiers and server-initiated streams use
|
||||||
|
// even.
|
||||||
|
nextStreamId = serverEndpoint ? 2 : 3;
|
||||||
|
}
|
||||||
|
|
||||||
// Update the next and last stream IDs.
|
@Override
|
||||||
nextStreamId += 2;
|
public DefaultStream createStream(int streamId, int priority, boolean halfClosed)
|
||||||
lastStreamCreated = streamId;
|
throws Http2Exception {
|
||||||
|
checkNewStreamAllowed(streamId);
|
||||||
|
|
||||||
// Register the stream.
|
// Create and initialize the stream.
|
||||||
streamMap.put(streamId, stream);
|
DefaultStream stream = new DefaultStream(streamId);
|
||||||
|
stream.setPriority(priority);
|
||||||
|
if (halfClosed) {
|
||||||
|
stream.state = isLocal() ? State.HALF_CLOSED_LOCAL : State.HALF_CLOSED_REMOTE;
|
||||||
|
} else {
|
||||||
|
stream.state = State.OPEN;
|
||||||
|
}
|
||||||
|
|
||||||
notifyStreamCreated(streamId);
|
// Update the next and last stream IDs.
|
||||||
return stream;
|
nextStreamId += 2;
|
||||||
|
lastStreamCreated = streamId;
|
||||||
|
|
||||||
|
// Register the stream and mark it as active.
|
||||||
|
streamMap.put(streamId, stream);
|
||||||
|
activeStreams.add(stream);
|
||||||
|
|
||||||
|
notifyStreamCreated(streamId);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultStream reservePushStream(int streamId, Http2Stream parent) throws Http2Exception {
|
||||||
|
if (parent == null) {
|
||||||
|
throw protocolError("Parent stream missing");
|
||||||
|
}
|
||||||
|
if (isLocal() ? !parent.isLocalSideOpen() : !parent.isRemoteSideOpen()) {
|
||||||
|
throw protocolError("Stream %d is not open for sending push promise", parent.getId());
|
||||||
|
}
|
||||||
|
if (!opposite().isPushToAllowed()) {
|
||||||
|
throw protocolError("Server push not allowed to opposite endpoint.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and initialize the stream.
|
||||||
|
DefaultStream stream = new DefaultStream(streamId);
|
||||||
|
stream.setPriority(parent.getPriority() + 1);
|
||||||
|
stream.state = isLocal() ? State.RESERVED_LOCAL : State.RESERVED_REMOTE;
|
||||||
|
|
||||||
|
// Update the next and last stream IDs.
|
||||||
|
nextStreamId += 2;
|
||||||
|
lastStreamCreated = streamId;
|
||||||
|
|
||||||
|
// Register the stream.
|
||||||
|
streamMap.put(streamId, stream);
|
||||||
|
|
||||||
|
notifyStreamCreated(streamId);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPushToAllowed(boolean allow) {
|
||||||
|
this.pushToAllowed = allow;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPushToAllowed() {
|
||||||
|
return pushToAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxStreams() {
|
||||||
|
return maxStreams;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMaxStreams(int maxStreams) {
|
||||||
|
this.maxStreams = maxStreams;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLastStreamCreated() {
|
||||||
|
return lastStreamCreated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Endpoint opposite() {
|
||||||
|
return isLocal() ? remoteEndpoint : localEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkNewStreamAllowed(int streamId) throws Http2Exception {
|
||||||
|
if (isGoAway()) {
|
||||||
|
throw protocolError("Cannot create a stream since the connection is going away");
|
||||||
|
}
|
||||||
|
if (nextStreamId < 0) {
|
||||||
|
throw protocolError("No more streams can be created on this connection");
|
||||||
|
}
|
||||||
|
if (streamId != nextStreamId) {
|
||||||
|
throw protocolError("Incorrect next stream ID requested: %d", streamId);
|
||||||
|
}
|
||||||
|
if (streamMap.size() + 1 > maxStreams) {
|
||||||
|
// TODO(nathanmittler): is this right?
|
||||||
|
throw protocolError("Maximum streams exceeded for this endpoint.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLocal() {
|
||||||
|
return this == localEndpoint;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPushToAllowed(boolean allow) {
|
|
||||||
this.pushToAllowed = allow;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPushToAllowed() {
|
|
||||||
return pushToAllowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxStreams() {
|
|
||||||
return maxStreams;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMaxStreams(int maxStreams) {
|
|
||||||
this.maxStreams = maxStreams;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLastStreamCreated() {
|
|
||||||
return lastStreamCreated;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Endpoint opposite() {
|
|
||||||
return isLocal() ? remoteEndpoint : localEndpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkNewStreamAllowed(int streamId) throws Http2Exception {
|
|
||||||
if (isGoAway()) {
|
|
||||||
throw protocolError("Cannot create a stream since the connection is going away");
|
|
||||||
}
|
|
||||||
if (nextStreamId < 0) {
|
|
||||||
throw protocolError("No more streams can be created on this connection");
|
|
||||||
}
|
|
||||||
if (streamId != nextStreamId) {
|
|
||||||
throw protocolError("Incorrect next stream ID requested: %d", streamId);
|
|
||||||
}
|
|
||||||
if (streamMap.size() + 1 > maxStreams) {
|
|
||||||
// TODO(nathanmittler): is this right?
|
|
||||||
throw protocolError("Maximum streams exceeded for this endpoint.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isLocal() {
|
|
||||||
return this == localEndpoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import static io.netty.handler.codec.http2.draft10.Http2Exception.flowControlErr
|
|||||||
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
||||||
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.CONNECTION_STREAM_ID;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.CONNECTION_STREAM_ID;
|
||||||
|
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2WindowUpdateFrame;
|
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2WindowUpdateFrame;
|
||||||
import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
|
import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
|
||||||
@ -33,178 +34,178 @@ import com.google.common.collect.Maps;
|
|||||||
*/
|
*/
|
||||||
public class DefaultInboundFlowController implements InboundFlowController {
|
public class DefaultInboundFlowController implements InboundFlowController {
|
||||||
|
|
||||||
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
||||||
private StreamWindow connectionWindow = new StreamWindow(CONNECTION_STREAM_ID);
|
private StreamWindow connectionWindow = new StreamWindow(CONNECTION_STREAM_ID);
|
||||||
private final Map<Integer, StreamWindow> streamWindows = Maps.newHashMap();
|
private final Map<Integer, StreamWindow> streamWindows = Maps.newHashMap();
|
||||||
|
|
||||||
public DefaultInboundFlowController(Http2Connection connection) {
|
public DefaultInboundFlowController(Http2Connection connection) {
|
||||||
connection.addListener(new Http2Connection.Listener() {
|
connection.addListener(new Http2Connection.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void streamCreated(int streamId) {
|
public void streamCreated(int streamId) {
|
||||||
streamWindows.put(streamId, new StreamWindow(streamId));
|
streamWindows.put(streamId, new StreamWindow(streamId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void streamClosed(int streamId) {
|
public void streamClosed(int streamId) {
|
||||||
streamWindows.remove(streamId);
|
streamWindows.remove(streamId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setInitialInboundWindowSize(int newWindowSize) throws Http2Exception {
|
|
||||||
int deltaWindowSize = newWindowSize - initialWindowSize;
|
|
||||||
initialWindowSize = newWindowSize;
|
|
||||||
|
|
||||||
// Apply the delta to all of the windows.
|
|
||||||
connectionWindow.addAndGet(deltaWindowSize);
|
|
||||||
for (StreamWindow window : streamWindows.values()) {
|
|
||||||
window.updatedInitialWindowSize(deltaWindowSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void applyInboundFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
|
|
||||||
throws Http2Exception {
|
|
||||||
applyConnectionFlowControl(dataFrame, frameWriter);
|
|
||||||
applyStreamFlowControl(dataFrame, frameWriter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply connection-wide flow control to the incoming data frame.
|
|
||||||
*/
|
|
||||||
private void applyConnectionFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
|
|
||||||
throws Http2Exception {
|
|
||||||
// Remove the data length from the available window size. Throw if the lower bound
|
|
||||||
// was exceeded.
|
|
||||||
connectionWindow.addAndGet(-dataFrame.content().readableBytes());
|
|
||||||
|
|
||||||
// If less than the window update threshold remains, restore the window size
|
|
||||||
// to the initial value and send a window update to the remote endpoint indicating
|
|
||||||
// the new window size.
|
|
||||||
if (connectionWindow.getSize() <= getWindowUpdateThreshold()) {
|
|
||||||
connectionWindow.updateWindow(frameWriter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply stream-based flow control to the incoming data frame.
|
|
||||||
*/
|
|
||||||
private void applyStreamFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
|
|
||||||
throws Http2Exception {
|
|
||||||
// Remove the data length from the available window size. Throw if the lower bound
|
|
||||||
// was exceeded.
|
|
||||||
StreamWindow window = getWindowOrFail(dataFrame.getStreamId());
|
|
||||||
window.addAndGet(-dataFrame.content().readableBytes());
|
|
||||||
|
|
||||||
// If less than the window update threshold remains, restore the window size
|
|
||||||
// to the initial value and send a window update to the remote endpoint indicating
|
|
||||||
// the new window size.
|
|
||||||
if (window.getSize() <= getWindowUpdateThreshold() && !dataFrame.isEndOfStream()) {
|
|
||||||
window.updateWindow(frameWriter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the threshold for a window size below which a window update should be issued.
|
|
||||||
*/
|
|
||||||
private int getWindowUpdateThreshold() {
|
|
||||||
return initialWindowSize / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the window for the stream or raises a {@code PROTOCOL_ERROR} if not found.
|
|
||||||
*/
|
|
||||||
private StreamWindow getWindowOrFail(int streamId) throws Http2Exception {
|
|
||||||
StreamWindow window = streamWindows.get(streamId);
|
|
||||||
if (window == null) {
|
|
||||||
throw protocolError("Flow control window missing for stream: %d", streamId);
|
|
||||||
}
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flow control window state for an individual stream.
|
|
||||||
*/
|
|
||||||
private final class StreamWindow {
|
|
||||||
private int windowSize;
|
|
||||||
private int lowerBound;
|
|
||||||
private final int streamId;
|
|
||||||
|
|
||||||
public StreamWindow(int streamId) {
|
|
||||||
this.streamId = streamId;
|
|
||||||
this.windowSize = initialWindowSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSize() {
|
@Override
|
||||||
return windowSize;
|
public void setInitialInboundWindowSize(int newWindowSize) throws Http2Exception {
|
||||||
}
|
int deltaWindowSize = newWindowSize - initialWindowSize;
|
||||||
|
initialWindowSize = newWindowSize;
|
||||||
|
|
||||||
/**
|
// Apply the delta to all of the windows.
|
||||||
* Adds the given delta to the window size and returns the new value.
|
connectionWindow.addAndGet(deltaWindowSize);
|
||||||
*
|
for (StreamWindow window : streamWindows.values()) {
|
||||||
* @param delta the delta in the initial window size.
|
window.updatedInitialWindowSize(deltaWindowSize);
|
||||||
* @throws Http2Exception thrown if the new window is less than the allowed lower bound.
|
|
||||||
*/
|
|
||||||
public int addAndGet(int delta) throws Http2Exception {
|
|
||||||
// Apply the delta. Even if we throw an exception we want to have taken this delta into
|
|
||||||
// account.
|
|
||||||
windowSize += delta;
|
|
||||||
if (delta > 0) {
|
|
||||||
lowerBound = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Window size can become negative if we sent a SETTINGS frame that reduces the
|
|
||||||
// size of the transfer window after the peer has written data frames.
|
|
||||||
// The value is bounded by the length that SETTINGS frame decrease the window.
|
|
||||||
// This difference is stored for the connection when writing the SETTINGS frame
|
|
||||||
// and is cleared once we send a WINDOW_UPDATE frame.
|
|
||||||
if (windowSize < lowerBound) {
|
|
||||||
if (streamId == CONNECTION_STREAM_ID) {
|
|
||||||
throw protocolError("Connection flow control window exceeded");
|
|
||||||
} else {
|
|
||||||
throw flowControlError("Flow control window exceeded for stream: %d", streamId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return windowSize;
|
@Override
|
||||||
|
public void applyInboundFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
|
||||||
|
throws Http2Exception {
|
||||||
|
applyConnectionFlowControl(dataFrame, frameWriter);
|
||||||
|
applyStreamFlowControl(dataFrame, frameWriter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when sending a SETTINGS frame with a new initial window size. If the window has gotten
|
* Apply connection-wide flow control to the incoming data frame.
|
||||||
* smaller (i.e. deltaWindowSize < 0), the lower bound is set to that value. This will
|
|
||||||
* temporarily allow for receipt of data frames which were sent by the remote endpoint before
|
|
||||||
* receiving the SETTINGS frame.
|
|
||||||
*
|
|
||||||
* @param delta the delta in the initial window size.
|
|
||||||
* @throws Http2Exception thrown if integer overflow occurs on the window.
|
|
||||||
*/
|
*/
|
||||||
public void updatedInitialWindowSize(int delta) throws Http2Exception {
|
private void applyConnectionFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
|
||||||
windowSize += delta;
|
throws Http2Exception {
|
||||||
if (delta > 0 && windowSize < Integer.MIN_VALUE + delta) {
|
// Remove the data length from the available window size. Throw if the lower bound
|
||||||
// Integer overflow.
|
// was exceeded.
|
||||||
throw flowControlError("Flow control window overflowed for stream: %d", streamId);
|
connectionWindow.addAndGet(-dataFrame.content().readableBytes());
|
||||||
}
|
|
||||||
|
|
||||||
if (delta < 0) {
|
// If less than the window update threshold remains, restore the window size
|
||||||
lowerBound = delta;
|
// to the initial value and send a window update to the remote endpoint indicating
|
||||||
}
|
// the new window size.
|
||||||
|
if (connectionWindow.getSize() <= getWindowUpdateThreshold()) {
|
||||||
|
connectionWindow.updateWindow(frameWriter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to perform a window update for this stream (or connection). Updates the window size
|
* Apply stream-based flow control to the incoming data frame.
|
||||||
* back to the size of the initial window and sends a window update frame to the remote
|
|
||||||
* endpoint.
|
|
||||||
*/
|
*/
|
||||||
public void updateWindow(FrameWriter frameWriter) throws Http2Exception {
|
private void applyStreamFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
|
||||||
// Expand the window for this stream back to the size of the initial window.
|
throws Http2Exception {
|
||||||
int deltaWindowSize = initialWindowSize - getSize();
|
// Remove the data length from the available window size. Throw if the lower bound
|
||||||
addAndGet(deltaWindowSize);
|
// was exceeded.
|
||||||
|
StreamWindow window = getWindowOrFail(dataFrame.getStreamId());
|
||||||
|
window.addAndGet(-dataFrame.content().readableBytes());
|
||||||
|
|
||||||
// Send a window update for the stream/connection.
|
// If less than the window update threshold remains, restore the window size
|
||||||
Http2WindowUpdateFrame updateFrame = new DefaultHttp2WindowUpdateFrame.Builder()
|
// to the initial value and send a window update to the remote endpoint indicating
|
||||||
.setStreamId(streamId).setWindowSizeIncrement(deltaWindowSize).build();
|
// the new window size.
|
||||||
frameWriter.writeFrame(updateFrame);
|
if (window.getSize() <= getWindowUpdateThreshold() && !dataFrame.isEndOfStream()) {
|
||||||
|
window.updateWindow(frameWriter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the threshold for a window size below which a window update should be issued.
|
||||||
|
*/
|
||||||
|
private int getWindowUpdateThreshold() {
|
||||||
|
return initialWindowSize / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the window for the stream or raises a {@code PROTOCOL_ERROR} if not found.
|
||||||
|
*/
|
||||||
|
private StreamWindow getWindowOrFail(int streamId) throws Http2Exception {
|
||||||
|
StreamWindow window = streamWindows.get(streamId);
|
||||||
|
if (window == null) {
|
||||||
|
throw protocolError("Flow control window missing for stream: %d", streamId);
|
||||||
|
}
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flow control window state for an individual stream.
|
||||||
|
*/
|
||||||
|
private final class StreamWindow {
|
||||||
|
private int windowSize;
|
||||||
|
private int lowerBound;
|
||||||
|
private final int streamId;
|
||||||
|
|
||||||
|
public StreamWindow(int streamId) {
|
||||||
|
this.streamId = streamId;
|
||||||
|
this.windowSize = initialWindowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSize() {
|
||||||
|
return windowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given delta to the window size and returns the new value.
|
||||||
|
*
|
||||||
|
* @param delta the delta in the initial window size.
|
||||||
|
* @throws Http2Exception thrown if the new window is less than the allowed lower bound.
|
||||||
|
*/
|
||||||
|
public int addAndGet(int delta) throws Http2Exception {
|
||||||
|
// Apply the delta. Even if we throw an exception we want to have taken this delta into
|
||||||
|
// account.
|
||||||
|
windowSize += delta;
|
||||||
|
if (delta > 0) {
|
||||||
|
lowerBound = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window size can become negative if we sent a SETTINGS frame that reduces the
|
||||||
|
// size of the transfer window after the peer has written data frames.
|
||||||
|
// The value is bounded by the length that SETTINGS frame decrease the window.
|
||||||
|
// This difference is stored for the connection when writing the SETTINGS frame
|
||||||
|
// and is cleared once we send a WINDOW_UPDATE frame.
|
||||||
|
if (windowSize < lowerBound) {
|
||||||
|
if (streamId == CONNECTION_STREAM_ID) {
|
||||||
|
throw protocolError("Connection flow control window exceeded");
|
||||||
|
} else {
|
||||||
|
throw flowControlError("Flow control window exceeded for stream: %d", streamId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return windowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when sending a SETTINGS frame with a new initial window size. If the window has gotten
|
||||||
|
* smaller (i.e. deltaWindowSize < 0), the lower bound is set to that value. This will
|
||||||
|
* temporarily allow for receipt of data frames which were sent by the remote endpoint before
|
||||||
|
* receiving the SETTINGS frame.
|
||||||
|
*
|
||||||
|
* @param delta the delta in the initial window size.
|
||||||
|
* @throws Http2Exception thrown if integer overflow occurs on the window.
|
||||||
|
*/
|
||||||
|
public void updatedInitialWindowSize(int delta) throws Http2Exception {
|
||||||
|
windowSize += delta;
|
||||||
|
if (delta > 0 && windowSize < Integer.MIN_VALUE + delta) {
|
||||||
|
// Integer overflow.
|
||||||
|
throw flowControlError("Flow control window overflowed for stream: %d", streamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta < 0) {
|
||||||
|
lowerBound = delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to perform a window update for this stream (or connection). Updates the window size
|
||||||
|
* back to the size of the initial window and sends a window update frame to the remote
|
||||||
|
* endpoint.
|
||||||
|
*/
|
||||||
|
public void updateWindow(FrameWriter frameWriter) throws Http2Exception {
|
||||||
|
// Expand the window for this stream back to the size of the initial window.
|
||||||
|
int deltaWindowSize = initialWindowSize - getSize();
|
||||||
|
addAndGet(deltaWindowSize);
|
||||||
|
|
||||||
|
// Send a window update for the stream/connection.
|
||||||
|
Http2WindowUpdateFrame updateFrame = new DefaultHttp2WindowUpdateFrame.Builder()
|
||||||
|
.setStreamId(streamId).setWindowSizeIncrement(deltaWindowSize).build();
|
||||||
|
frameWriter.writeFrame(updateFrame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
|
|||||||
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
||||||
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.CONNECTION_STREAM_ID;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.CONNECTION_STREAM_ID;
|
||||||
|
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2StreamException;
|
import io.netty.handler.codec.http2.draft10.Http2StreamException;
|
||||||
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2DataFrame;
|
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2DataFrame;
|
||||||
@ -37,255 +38,255 @@ import com.google.common.collect.Maps;
|
|||||||
*/
|
*/
|
||||||
public class DefaultOutboundFlowController implements OutboundFlowController {
|
public class DefaultOutboundFlowController implements OutboundFlowController {
|
||||||
|
|
||||||
private final Http2Connection connection;
|
private final Http2Connection connection;
|
||||||
private final Map<Integer, StreamState> streamStates = Maps.newHashMap();
|
private final Map<Integer, StreamState> streamStates = Maps.newHashMap();
|
||||||
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
||||||
private int connectionWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
private int connectionWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
||||||
|
|
||||||
public DefaultOutboundFlowController(Http2Connection connection) {
|
public DefaultOutboundFlowController(Http2Connection connection) {
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
connection.addListener(new Http2Connection.Listener() {
|
connection.addListener(new Http2Connection.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void streamCreated(int streamId) {
|
public void streamCreated(int streamId) {
|
||||||
streamStates.put(streamId, new StreamState(streamId));
|
streamStates.put(streamId, new StreamState(streamId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void streamClosed(int streamId) {
|
public void streamClosed(int streamId) {
|
||||||
StreamState state = streamStates.remove(streamId);
|
StreamState state = streamStates.remove(streamId);
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
state.clearPendingWrites();
|
state.clearPendingWrites();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInitialOutboundWindowSize(int newWindowSize) throws Http2Exception {
|
||||||
|
int delta = newWindowSize - initialWindowSize;
|
||||||
|
initialWindowSize = newWindowSize;
|
||||||
|
addAndGetConnectionWindowSize(delta);
|
||||||
|
for (StreamState window : streamStates.values()) {
|
||||||
|
// Verify that the maximum value is not exceeded by this change.
|
||||||
|
window.addAndGetWindow(delta);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
if (delta > 0) {
|
||||||
public void setInitialOutboundWindowSize(int newWindowSize) throws Http2Exception {
|
// The window size increased, send any pending frames for all streams.
|
||||||
int delta = newWindowSize - initialWindowSize;
|
writePendingFrames();
|
||||||
initialWindowSize = newWindowSize;
|
|
||||||
addAndGetConnectionWindowSize(delta);
|
|
||||||
for (StreamState window : streamStates.values()) {
|
|
||||||
// Verify that the maximum value is not exceeded by this change.
|
|
||||||
window.addAndGetWindow(delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delta > 0) {
|
|
||||||
// The window size increased, send any pending frames for all streams.
|
|
||||||
writePendingFrames();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateOutboundWindowSize(int streamId, int delta) throws Http2Exception {
|
|
||||||
StreamState streamWindow = null;
|
|
||||||
if (streamId == CONNECTION_STREAM_ID) {
|
|
||||||
// Update the connection window and write any pending frames for all streams.
|
|
||||||
addAndGetConnectionWindowSize(delta);
|
|
||||||
writePendingFrames();
|
|
||||||
} else {
|
|
||||||
// Update the stream window and write any pending frames for the stream.
|
|
||||||
streamWindow = getStateOrFail(streamId);
|
|
||||||
streamWindow.addAndGetWindow(delta);
|
|
||||||
streamWindow.writePendingFrames(Integer.MAX_VALUE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendFlowControlled(Http2DataFrame frame, FrameWriter frameWriter)
|
|
||||||
throws Http2Exception {
|
|
||||||
|
|
||||||
StreamState streamState = getStateOrFail(frame.getStreamId());
|
|
||||||
|
|
||||||
int dataLength = frame.content().readableBytes();
|
|
||||||
if (streamState.writableWindow() >= dataLength) {
|
|
||||||
// Window size is large enough to send entire data frame
|
|
||||||
writeFrame(frame, streamState, frameWriter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enqueue the frame to be written when the window size permits.
|
|
||||||
streamState.addPendingWrite(new PendingWrite(frame, frameWriter));
|
|
||||||
|
|
||||||
if (streamState.writableWindow() <= 0) {
|
|
||||||
// Stream is stalled, don't send anything now.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and send a partial frame up to the window size.
|
|
||||||
Http2DataFrame partialFrame = readPartialFrame(frame, streamState.writableWindow());
|
|
||||||
writeFrame(partialFrame, streamState, frameWriter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to get the {@link StreamState} for the given stream. If not available, raises a
|
|
||||||
* {@code PROTOCOL_ERROR}.
|
|
||||||
*/
|
|
||||||
private StreamState getStateOrFail(int streamId) throws Http2Exception {
|
|
||||||
StreamState streamState = streamStates.get(streamId);
|
|
||||||
if (streamState == null) {
|
|
||||||
throw protocolError("Missing flow control window for stream: %d", streamId);
|
|
||||||
}
|
|
||||||
return streamState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the frame and decrements the stream and connection window sizes.
|
|
||||||
*/
|
|
||||||
private void writeFrame(Http2DataFrame frame, StreamState state, FrameWriter frameWriter)
|
|
||||||
throws Http2Exception {
|
|
||||||
int dataLength = frame.content().readableBytes();
|
|
||||||
connectionWindowSize -= dataLength;
|
|
||||||
state.addAndGetWindow(-dataLength);
|
|
||||||
frameWriter.writeFrame(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a view of the given frame starting at the current read index with the given number of
|
|
||||||
* bytes. The reader index on the input frame is then advanced by the number of bytes. The
|
|
||||||
* returned frame will not have end-of-stream set.
|
|
||||||
*/
|
|
||||||
private Http2DataFrame readPartialFrame(Http2DataFrame frame, int numBytes) {
|
|
||||||
return new DefaultHttp2DataFrame.Builder().setStreamId(frame.getStreamId())
|
|
||||||
.setContent(frame.content().readSlice(numBytes).retain()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether applying the delta to the given value will cause an integer overflow.
|
|
||||||
*/
|
|
||||||
private boolean isIntegerOverflow(int previousValue, int delta) {
|
|
||||||
return delta > 0 && (Integer.MAX_VALUE - delta) < previousValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increments the connectionWindowSize and returns the new value.
|
|
||||||
*/
|
|
||||||
private int addAndGetConnectionWindowSize(int delta) throws Http2Exception {
|
|
||||||
if (isIntegerOverflow(connectionWindowSize, delta)) {
|
|
||||||
throw format(FLOW_CONTROL_ERROR, "Window update exceeds maximum for connection");
|
|
||||||
}
|
|
||||||
return connectionWindowSize += delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes any pending frames for the entire connection.
|
|
||||||
*/
|
|
||||||
private void writePendingFrames() throws Http2Exception {
|
|
||||||
// The request for for the entire connection, write any pending frames across
|
|
||||||
// all active streams. Active streams are already sorted by their priority.
|
|
||||||
for (Http2Stream stream : connection.getActiveStreams()) {
|
|
||||||
StreamState state = getStateOrFail(stream.getId());
|
|
||||||
state.writePendingFrames(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The outbound flow control state for a single stream.
|
|
||||||
*/
|
|
||||||
private class StreamState {
|
|
||||||
private final int streamId;
|
|
||||||
private final Queue<PendingWrite> pendingWriteQueue = Lists.newLinkedList();
|
|
||||||
private int windowSize = initialWindowSize;
|
|
||||||
|
|
||||||
public StreamState(int streamId) {
|
|
||||||
this.streamId = streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int addAndGetWindow(int delta) throws Http2Exception {
|
|
||||||
if (isIntegerOverflow(windowSize, delta)) {
|
|
||||||
throw new Http2StreamException(streamId, FLOW_CONTROL_ERROR,
|
|
||||||
"Window size overflow for stream");
|
|
||||||
}
|
|
||||||
windowSize += delta;
|
|
||||||
return windowSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int writableWindow() {
|
|
||||||
return Math.min(windowSize, connectionWindowSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addPendingWrite(PendingWrite pendingWrite) {
|
|
||||||
pendingWriteQueue.offer(pendingWrite);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasPendingWrite() {
|
|
||||||
return !pendingWriteQueue.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PendingWrite peekPendingWrite() {
|
|
||||||
if (windowSize > 0) {
|
|
||||||
return pendingWriteQueue.peek();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removePendingWrite() {
|
|
||||||
pendingWriteQueue.poll();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearPendingWrites() {
|
|
||||||
while (true) {
|
|
||||||
PendingWrite pendingWrite = pendingWriteQueue.poll();
|
|
||||||
if (pendingWrite == null) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
pendingWrite.writeError(
|
}
|
||||||
format(STREAM_CLOSED, "Stream closed before write could take place"));
|
|
||||||
}
|
@Override
|
||||||
|
public void updateOutboundWindowSize(int streamId, int delta) throws Http2Exception {
|
||||||
|
StreamState streamWindow = null;
|
||||||
|
if (streamId == CONNECTION_STREAM_ID) {
|
||||||
|
// Update the connection window and write any pending frames for all streams.
|
||||||
|
addAndGetConnectionWindowSize(delta);
|
||||||
|
writePendingFrames();
|
||||||
|
} else {
|
||||||
|
// Update the stream window and write any pending frames for the stream.
|
||||||
|
streamWindow = getStateOrFail(streamId);
|
||||||
|
streamWindow.addAndGetWindow(delta);
|
||||||
|
streamWindow.writePendingFrames(Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendFlowControlled(Http2DataFrame frame, FrameWriter frameWriter)
|
||||||
|
throws Http2Exception {
|
||||||
|
|
||||||
|
StreamState streamState = getStateOrFail(frame.getStreamId());
|
||||||
|
|
||||||
|
int dataLength = frame.content().readableBytes();
|
||||||
|
if (streamState.writableWindow() >= dataLength) {
|
||||||
|
// Window size is large enough to send entire data frame
|
||||||
|
writeFrame(frame, streamState, frameWriter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueue the frame to be written when the window size permits.
|
||||||
|
streamState.addPendingWrite(new PendingWrite(frame, frameWriter));
|
||||||
|
|
||||||
|
if (streamState.writableWindow() <= 0) {
|
||||||
|
// Stream is stalled, don't send anything now.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and send a partial frame up to the window size.
|
||||||
|
Http2DataFrame partialFrame = readPartialFrame(frame, streamState.writableWindow());
|
||||||
|
writeFrame(partialFrame, streamState, frameWriter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends all pending writes for this stream so long as there is space the the stream and
|
* Attempts to get the {@link StreamState} for the given stream. If not available, raises a
|
||||||
* connection windows.
|
* {@code PROTOCOL_ERROR}.
|
||||||
*
|
|
||||||
* @param maxFrames the maximum number of frames to send.
|
|
||||||
*/
|
*/
|
||||||
public void writePendingFrames(int maxFrames) throws Http2Exception {
|
private StreamState getStateOrFail(int streamId) throws Http2Exception {
|
||||||
while (maxFrames > 0 && writableWindow() > 0 && hasPendingWrite()) {
|
StreamState streamState = streamStates.get(streamId);
|
||||||
maxFrames--;
|
if (streamState == null) {
|
||||||
PendingWrite pendingWrite = peekPendingWrite();
|
throw protocolError("Missing flow control window for stream: %d", streamId);
|
||||||
|
|
||||||
if (writableWindow() >= pendingWrite.size()) {
|
|
||||||
// Window size is large enough to send entire data frame
|
|
||||||
removePendingWrite();
|
|
||||||
writeFrame(pendingWrite.frame(), this, pendingWrite.writer());
|
|
||||||
} else {
|
|
||||||
// We can send a partial frame
|
|
||||||
Http2DataFrame partialDataFrame =
|
|
||||||
readPartialFrame(pendingWrite.frame(), writableWindow());
|
|
||||||
writeFrame(partialDataFrame, this, pendingWrite.writer());
|
|
||||||
}
|
}
|
||||||
}
|
return streamState;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pending write for a single data frame.
|
|
||||||
*/
|
|
||||||
private class PendingWrite {
|
|
||||||
private final Http2DataFrame frame;
|
|
||||||
private final FrameWriter writer;
|
|
||||||
|
|
||||||
public PendingWrite(Http2DataFrame frame, FrameWriter writer) {
|
|
||||||
this.frame = frame;
|
|
||||||
this.writer = writer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Http2DataFrame frame() {
|
/**
|
||||||
return frame;
|
* Writes the frame and decrements the stream and connection window sizes.
|
||||||
|
*/
|
||||||
|
private void writeFrame(Http2DataFrame frame, StreamState state, FrameWriter frameWriter)
|
||||||
|
throws Http2Exception {
|
||||||
|
int dataLength = frame.content().readableBytes();
|
||||||
|
connectionWindowSize -= dataLength;
|
||||||
|
state.addAndGetWindow(-dataLength);
|
||||||
|
frameWriter.writeFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FrameWriter writer() {
|
/**
|
||||||
return writer;
|
* Creates a view of the given frame starting at the current read index with the given number of
|
||||||
|
* bytes. The reader index on the input frame is then advanced by the number of bytes. The
|
||||||
|
* returned frame will not have end-of-stream set.
|
||||||
|
*/
|
||||||
|
private Http2DataFrame readPartialFrame(Http2DataFrame frame, int numBytes) {
|
||||||
|
return new DefaultHttp2DataFrame.Builder().setStreamId(frame.getStreamId())
|
||||||
|
.setContent(frame.content().readSlice(numBytes).retain()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int size() {
|
/**
|
||||||
return frame.content().readableBytes();
|
* Indicates whether applying the delta to the given value will cause an integer overflow.
|
||||||
|
*/
|
||||||
|
private boolean isIntegerOverflow(int previousValue, int delta) {
|
||||||
|
return delta > 0 && (Integer.MAX_VALUE - delta) < previousValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeError(Http2Exception cause) {
|
/**
|
||||||
frame.release();
|
* Increments the connectionWindowSize and returns the new value.
|
||||||
writer.setFailure(cause);
|
*/
|
||||||
|
private int addAndGetConnectionWindowSize(int delta) throws Http2Exception {
|
||||||
|
if (isIntegerOverflow(connectionWindowSize, delta)) {
|
||||||
|
throw format(FLOW_CONTROL_ERROR, "Window update exceeds maximum for connection");
|
||||||
|
}
|
||||||
|
return connectionWindowSize += delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes any pending frames for the entire connection.
|
||||||
|
*/
|
||||||
|
private void writePendingFrames() throws Http2Exception {
|
||||||
|
// The request for for the entire connection, write any pending frames across
|
||||||
|
// all active streams. Active streams are already sorted by their priority.
|
||||||
|
for (Http2Stream stream : connection.getActiveStreams()) {
|
||||||
|
StreamState state = getStateOrFail(stream.getId());
|
||||||
|
state.writePendingFrames(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The outbound flow control state for a single stream.
|
||||||
|
*/
|
||||||
|
private class StreamState {
|
||||||
|
private final int streamId;
|
||||||
|
private final Queue<PendingWrite> pendingWriteQueue = Lists.newLinkedList();
|
||||||
|
private int windowSize = initialWindowSize;
|
||||||
|
|
||||||
|
public StreamState(int streamId) {
|
||||||
|
this.streamId = streamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int addAndGetWindow(int delta) throws Http2Exception {
|
||||||
|
if (isIntegerOverflow(windowSize, delta)) {
|
||||||
|
throw new Http2StreamException(streamId, FLOW_CONTROL_ERROR,
|
||||||
|
"Window size overflow for stream");
|
||||||
|
}
|
||||||
|
windowSize += delta;
|
||||||
|
return windowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int writableWindow() {
|
||||||
|
return Math.min(windowSize, connectionWindowSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPendingWrite(PendingWrite pendingWrite) {
|
||||||
|
pendingWriteQueue.offer(pendingWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPendingWrite() {
|
||||||
|
return !pendingWriteQueue.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PendingWrite peekPendingWrite() {
|
||||||
|
if (windowSize > 0) {
|
||||||
|
return pendingWriteQueue.peek();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removePendingWrite() {
|
||||||
|
pendingWriteQueue.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearPendingWrites() {
|
||||||
|
while (true) {
|
||||||
|
PendingWrite pendingWrite = pendingWriteQueue.poll();
|
||||||
|
if (pendingWrite == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pendingWrite.writeError(
|
||||||
|
format(STREAM_CLOSED, "Stream closed before write could take place"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends all pending writes for this stream so long as there is space the the stream and
|
||||||
|
* connection windows.
|
||||||
|
*
|
||||||
|
* @param maxFrames the maximum number of frames to send.
|
||||||
|
*/
|
||||||
|
public void writePendingFrames(int maxFrames) throws Http2Exception {
|
||||||
|
while (maxFrames > 0 && writableWindow() > 0 && hasPendingWrite()) {
|
||||||
|
maxFrames--;
|
||||||
|
PendingWrite pendingWrite = peekPendingWrite();
|
||||||
|
|
||||||
|
if (writableWindow() >= pendingWrite.size()) {
|
||||||
|
// Window size is large enough to send entire data frame
|
||||||
|
removePendingWrite();
|
||||||
|
writeFrame(pendingWrite.frame(), this, pendingWrite.writer());
|
||||||
|
} else {
|
||||||
|
// We can send a partial frame
|
||||||
|
Http2DataFrame partialDataFrame =
|
||||||
|
readPartialFrame(pendingWrite.frame(), writableWindow());
|
||||||
|
writeFrame(partialDataFrame, this, pendingWrite.writer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pending write for a single data frame.
|
||||||
|
*/
|
||||||
|
private class PendingWrite {
|
||||||
|
private final Http2DataFrame frame;
|
||||||
|
private final FrameWriter writer;
|
||||||
|
|
||||||
|
public PendingWrite(Http2DataFrame frame, FrameWriter writer) {
|
||||||
|
this.frame = frame;
|
||||||
|
this.writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Http2DataFrame frame() {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameWriter writer() {
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return frame.content().readableBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeError(Http2Exception cause) {
|
||||||
|
frame.release();
|
||||||
|
writer.setFailure(cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -23,152 +23,152 @@ import java.util.List;
|
|||||||
|
|
||||||
public interface Http2Connection {
|
public interface Http2Connection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A view of the connection from one endpoint (local or remote).
|
* A view of the connection from one endpoint (local or remote).
|
||||||
*/
|
*/
|
||||||
interface Endpoint {
|
interface Endpoint {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a stream initiated by this endpoint and notifies all listeners. This could fail for
|
||||||
|
* the following reasons:
|
||||||
|
* <p/>
|
||||||
|
* - The requested stream ID is not the next sequential ID for this endpoint. <br>
|
||||||
|
* - The stream already exists. <br>
|
||||||
|
* - The number of concurrent streams is above the allowed threshold for this endpoint. <br>
|
||||||
|
* - The connection is marked as going away}. <br>
|
||||||
|
* - The provided priority is < 0.
|
||||||
|
*
|
||||||
|
* @param streamId The ID of the stream
|
||||||
|
* @param priority the priority of the stream
|
||||||
|
* @param halfClosed if true, the stream is created in the half-closed state with respect to
|
||||||
|
* this endpoint. Otherwise it's created in the open state.
|
||||||
|
*/
|
||||||
|
Http2Stream createStream(int streamId, int priority, boolean halfClosed) throws Http2Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a push stream in the reserved state for this endpoint and notifies all listeners.
|
||||||
|
* This could fail for the following reasons:
|
||||||
|
* <p/>
|
||||||
|
* - Server push is not allowed to the opposite endpoint. <br>
|
||||||
|
* - The requested stream ID is not the next sequential stream ID for this endpoint. <br>
|
||||||
|
* - The number of concurrent streams is above the allowed threshold for this endpoint. <br>
|
||||||
|
* - The connection is marked as going away. <br>
|
||||||
|
* - The parent stream ID does not exist or is not open from the side sending the push promise.
|
||||||
|
* <br>
|
||||||
|
* - Could not set a valid priority for the new stream.
|
||||||
|
*
|
||||||
|
* @param streamId the ID of the push stream
|
||||||
|
* @param parent the parent stream used to initiate the push stream.
|
||||||
|
*/
|
||||||
|
Http2Stream reservePushStream(int streamId, Http2Stream parent) throws Http2Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether server push is allowed to this endpoint.
|
||||||
|
*/
|
||||||
|
void setPushToAllowed(boolean allow);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets whether or not server push is allowed to this endpoint.
|
||||||
|
*/
|
||||||
|
boolean isPushToAllowed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the maximum number of concurrent streams allowed by this endpoint.
|
||||||
|
*/
|
||||||
|
int getMaxStreams();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum number of concurrent streams allowed by this endpoint.
|
||||||
|
*/
|
||||||
|
void setMaxStreams(int maxStreams);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the ID of the stream last successfully created by this endpoint.
|
||||||
|
*/
|
||||||
|
int getLastStreamCreated();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link Endpoint} opposite this one.
|
||||||
|
*/
|
||||||
|
Endpoint opposite();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a stream initiated by this endpoint and notifies all listeners. This could fail for
|
* A listener of the connection for stream events.
|
||||||
* the following reasons:
|
|
||||||
* <p>
|
|
||||||
* - The requested stream ID is not the next sequential ID for this endpoint. <br>
|
|
||||||
* - The stream already exists. <br>
|
|
||||||
* - The number of concurrent streams is above the allowed threshold for this endpoint. <br>
|
|
||||||
* - The connection is marked as going away}. <br>
|
|
||||||
* - The provided priority is < 0.
|
|
||||||
*
|
|
||||||
* @param streamId The ID of the stream
|
|
||||||
* @param priority the priority of the stream
|
|
||||||
* @param halfClosed if true, the stream is created in the half-closed state with respect to
|
|
||||||
* this endpoint. Otherwise it's created in the open state.
|
|
||||||
*/
|
*/
|
||||||
Http2Stream createStream(int streamId, int priority, boolean halfClosed) throws Http2Exception;
|
interface Listener {
|
||||||
|
/**
|
||||||
|
* Called when a new stream with the given ID is created.
|
||||||
|
*/
|
||||||
|
void streamCreated(int streamId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the stream with the given ID is closed.
|
||||||
|
*/
|
||||||
|
void streamClosed(int streamId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a push stream in the reserved state for this endpoint and notifies all listeners.
|
* Adds a listener of this connection.
|
||||||
* This could fail for the following reasons:
|
|
||||||
* <p>
|
|
||||||
* - Server push is not allowed to the opposite endpoint. <br>
|
|
||||||
* - The requested stream ID is not the next sequential stream ID for this endpoint. <br>
|
|
||||||
* - The number of concurrent streams is above the allowed threshold for this endpoint. <br>
|
|
||||||
* - The connection is marked as going away. <br>
|
|
||||||
* - The parent stream ID does not exist or is not open from the side sending the push promise.
|
|
||||||
* <br>
|
|
||||||
* - Could not set a valid priority for the new stream.
|
|
||||||
*
|
|
||||||
* @param streamId the ID of the push stream
|
|
||||||
* @param parent the parent stream used to initiate the push stream.
|
|
||||||
*/
|
*/
|
||||||
Http2Stream reservePushStream(int streamId, Http2Stream parent) throws Http2Exception;
|
void addListener(Listener listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether server push is allowed to this endpoint.
|
* Removes a listener of this connection.
|
||||||
*/
|
*/
|
||||||
void setPushToAllowed(boolean allow);
|
void removeListener(Listener listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets whether or not server push is allowed to this endpoint.
|
* Attempts to get the stream for the given ID. If it doesn't exist, throws.
|
||||||
*/
|
*/
|
||||||
boolean isPushToAllowed();
|
Http2Stream getStreamOrFail(int streamId) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the maximum number of concurrent streams allowed by this endpoint.
|
* Gets the stream if it exists. If not, returns {@code null}.
|
||||||
*/
|
*/
|
||||||
int getMaxStreams();
|
Http2Stream getStream(int streamId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the maximum number of concurrent streams allowed by this endpoint.
|
* Gets all streams that are currently either open or half-closed. The returned collection is
|
||||||
|
* sorted by priority.
|
||||||
*/
|
*/
|
||||||
void setMaxStreams(int maxStreams);
|
List<Http2Stream> getActiveStreams();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the ID of the stream last successfully created by this endpoint.
|
* Gets a view of this connection from the local {@link Endpoint}.
|
||||||
*/
|
*/
|
||||||
int getLastStreamCreated();
|
Endpoint local();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the {@link Endpoint} opposite this one.
|
* Gets a view of this connection from the remote {@link Endpoint}.
|
||||||
*/
|
*/
|
||||||
Endpoint opposite();
|
Endpoint remote();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener of the connection for stream events.
|
|
||||||
*/
|
|
||||||
interface Listener {
|
|
||||||
/**
|
|
||||||
* Called when a new stream with the given ID is created.
|
|
||||||
*/
|
|
||||||
void streamCreated(int streamId);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the stream with the given ID is closed.
|
* Marks that a GoAway frame has been sent on this connection. After calling this, both
|
||||||
|
* {@link #isGoAwaySent()} and {@link #isGoAway()} will be {@code true}.
|
||||||
*/
|
*/
|
||||||
void streamClosed(int streamId);
|
void sendGoAway(ChannelHandlerContext ctx, ChannelPromise promise, Http2Exception cause);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a listener of this connection.
|
* Marks that a GoAway frame has been received on this connection. After calling this, both
|
||||||
*/
|
* {@link #isGoAwayReceived()} and {@link #isGoAway()} will be {@code true}.
|
||||||
void addListener(Listener listener);
|
*/
|
||||||
|
void goAwayReceived();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a listener of this connection.
|
* Indicates that this connection received a GoAway message.
|
||||||
*/
|
*/
|
||||||
void removeListener(Listener listener);
|
boolean isGoAwaySent();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to get the stream for the given ID. If it doesn't exist, throws.
|
* Indicates that this connection send a GoAway message.
|
||||||
*/
|
*/
|
||||||
Http2Stream getStreamOrFail(int streamId) throws Http2Exception;
|
boolean isGoAwayReceived();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the stream if it exists. If not, returns {@code null}.
|
* Indicates whether or not this endpoint is going away. This is a short form for
|
||||||
*/
|
* {@link #isGoAwaySent()} || {@link #isGoAwayReceived()}.
|
||||||
Http2Stream getStream(int streamId);
|
*/
|
||||||
|
boolean isGoAway();
|
||||||
/**
|
|
||||||
* Gets all streams that are currently either open or half-closed. The returned collection is
|
|
||||||
* sorted by priority.
|
|
||||||
*/
|
|
||||||
List<Http2Stream> getActiveStreams();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a view of this connection from the local {@link Endpoint}.
|
|
||||||
*/
|
|
||||||
Endpoint local();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a view of this connection from the remote {@link Endpoint}.
|
|
||||||
*/
|
|
||||||
Endpoint remote();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks that a GoAway frame has been sent on this connection. After calling this, both
|
|
||||||
* {@link #isGoAwaySent()} and {@link #isGoAway()} will be {@code true}.
|
|
||||||
*/
|
|
||||||
void sendGoAway(ChannelHandlerContext ctx, ChannelPromise promise, Http2Exception cause);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks that a GoAway frame has been received on this connection. After calling this, both
|
|
||||||
* {@link #isGoAwayReceived()} and {@link #isGoAway()} will be {@code true}.
|
|
||||||
*/
|
|
||||||
void goAwayReceived();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that this connection received a GoAway message.
|
|
||||||
*/
|
|
||||||
boolean isGoAwaySent();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that this connection send a GoAway message.
|
|
||||||
*/
|
|
||||||
boolean isGoAwayReceived();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether or not this endpoint is going away. This is a short form for
|
|
||||||
* {@link #isGoAwaySent()} || {@link #isGoAwayReceived()}.
|
|
||||||
*/
|
|
||||||
boolean isGoAway();
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.
|
|||||||
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.OPEN;
|
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.OPEN;
|
||||||
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.RESERVED_LOCAL;
|
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.RESERVED_LOCAL;
|
||||||
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.RESERVED_REMOTE;
|
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.RESERVED_REMOTE;
|
||||||
|
|
||||||
import io.netty.channel.ChannelFuture;
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelFutureListener;
|
import io.netty.channel.ChannelFutureListener;
|
||||||
import io.netty.channel.ChannelHandlerAdapter;
|
import io.netty.channel.ChannelHandlerAdapter;
|
||||||
@ -49,512 +50,512 @@ import io.netty.util.ReferenceCountUtil;
|
|||||||
|
|
||||||
public class Http2ConnectionHandler extends ChannelHandlerAdapter {
|
public class Http2ConnectionHandler extends ChannelHandlerAdapter {
|
||||||
|
|
||||||
private final Http2Connection connection;
|
private final Http2Connection connection;
|
||||||
private final InboundFlowController inboundFlow;
|
private final InboundFlowController inboundFlow;
|
||||||
private final OutboundFlowController outboundFlow;
|
private final OutboundFlowController outboundFlow;
|
||||||
|
|
||||||
public Http2ConnectionHandler(boolean server) {
|
public Http2ConnectionHandler(boolean server) {
|
||||||
this(new DefaultHttp2Connection(server));
|
this(new DefaultHttp2Connection(server));
|
||||||
}
|
|
||||||
|
|
||||||
public Http2ConnectionHandler(Http2Connection connection) {
|
|
||||||
this(connection, new DefaultInboundFlowController(connection),
|
|
||||||
new DefaultOutboundFlowController(connection));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Http2ConnectionHandler(final Http2Connection connection,
|
|
||||||
final InboundFlowController inboundFlow, final OutboundFlowController outboundFlow) {
|
|
||||||
this.connection = connection;
|
|
||||||
this.inboundFlow = inboundFlow;
|
|
||||||
this.outboundFlow = outboundFlow;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
|
||||||
// Avoid NotYetConnectedException
|
|
||||||
if (!ctx.channel().isActive()) {
|
|
||||||
ctx.close(promise);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.sendGoAway(ctx, promise, null);
|
public Http2ConnectionHandler(Http2Connection connection) {
|
||||||
}
|
this(connection, new DefaultInboundFlowController(connection),
|
||||||
|
new DefaultOutboundFlowController(connection));
|
||||||
@Override
|
|
||||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
for (Http2Stream stream : connection.getActiveStreams()) {
|
|
||||||
stream.close(ctx, ctx.newSucceededFuture());
|
|
||||||
}
|
|
||||||
ctx.fireChannelInactive();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles {@link Http2Exception} objects that were thrown from other handlers. Ignores all other
|
|
||||||
* exceptions.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
||||||
if (cause instanceof Http2Exception) {
|
|
||||||
processHttp2Exception(ctx, (Http2Exception) cause);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.fireExceptionCaught(cause);
|
public Http2ConnectionHandler(final Http2Connection connection,
|
||||||
}
|
final InboundFlowController inboundFlow, final OutboundFlowController outboundFlow) {
|
||||||
|
this.connection = connection;
|
||||||
@Override
|
this.inboundFlow = inboundFlow;
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object inMsg) throws Exception {
|
this.outboundFlow = outboundFlow;
|
||||||
try {
|
|
||||||
if (inMsg instanceof Http2DataFrame) {
|
|
||||||
handleInboundData(ctx, (Http2DataFrame) inMsg);
|
|
||||||
} else if (inMsg instanceof Http2HeadersFrame) {
|
|
||||||
handleInboundHeaders(ctx, (Http2HeadersFrame) inMsg);
|
|
||||||
} else if (inMsg instanceof Http2PushPromiseFrame) {
|
|
||||||
handleInboundPushPromise(ctx, (Http2PushPromiseFrame) inMsg);
|
|
||||||
} else if (inMsg instanceof Http2PriorityFrame) {
|
|
||||||
handleInboundPriority(ctx, (Http2PriorityFrame) inMsg);
|
|
||||||
} else if (inMsg instanceof Http2RstStreamFrame) {
|
|
||||||
handleInboundRstStream(ctx, (Http2RstStreamFrame) inMsg);
|
|
||||||
} else if (inMsg instanceof Http2PingFrame) {
|
|
||||||
handleInboundPing(ctx, (Http2PingFrame) inMsg);
|
|
||||||
} else if (inMsg instanceof Http2GoAwayFrame) {
|
|
||||||
handleInboundGoAway(ctx, (Http2GoAwayFrame) inMsg);
|
|
||||||
} else if (inMsg instanceof Http2WindowUpdateFrame) {
|
|
||||||
handleInboundWindowUpdate(ctx, (Http2WindowUpdateFrame) inMsg);
|
|
||||||
} else if (inMsg instanceof Http2SettingsFrame) {
|
|
||||||
handleInboundSettings(ctx, (Http2SettingsFrame) inMsg);
|
|
||||||
} else {
|
|
||||||
ctx.fireChannelRead(inMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Http2Exception e) {
|
|
||||||
ReferenceCountUtil.release(inMsg);
|
|
||||||
processHttp2Exception(ctx, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
|
|
||||||
throws Exception {
|
|
||||||
try {
|
|
||||||
if (msg instanceof Http2DataFrame) {
|
|
||||||
handleOutboundData(ctx, (Http2DataFrame) msg, promise);
|
|
||||||
} else if (msg instanceof Http2HeadersFrame) {
|
|
||||||
handleOutboundHeaders(ctx, (Http2HeadersFrame) msg, promise);
|
|
||||||
} else if (msg instanceof Http2PushPromiseFrame) {
|
|
||||||
handleOutboundPushPromise(ctx, (Http2PushPromiseFrame) msg, promise);
|
|
||||||
} else if (msg instanceof Http2PriorityFrame) {
|
|
||||||
handleOutboundPriority(ctx, (Http2PriorityFrame) msg, promise);
|
|
||||||
} else if (msg instanceof Http2RstStreamFrame) {
|
|
||||||
handleOutboundRstStream(ctx, (Http2RstStreamFrame) msg, promise);
|
|
||||||
} else if (msg instanceof Http2PingFrame) {
|
|
||||||
handleOutboundPing(ctx, (Http2PingFrame) msg, promise);
|
|
||||||
} else if (msg instanceof Http2GoAwayFrame) {
|
|
||||||
handleOutboundGoAway();
|
|
||||||
} else if (msg instanceof Http2WindowUpdateFrame) {
|
|
||||||
handleOutboundWindowUpdate();
|
|
||||||
} else if (msg instanceof Http2SettingsFrame) {
|
|
||||||
handleOutboundSettings(ctx, (Http2SettingsFrame) msg, promise);
|
|
||||||
} else {
|
|
||||||
ctx.write(msg, promise);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Throwable e) {
|
|
||||||
ReferenceCountUtil.release(msg);
|
|
||||||
promise.setFailure(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes the given exception. Depending on the type of exception, delegates to either
|
|
||||||
* {@link #processConnectionError} or {@link #processStreamError}.
|
|
||||||
*/
|
|
||||||
private void processHttp2Exception(ChannelHandlerContext ctx, Http2Exception e) {
|
|
||||||
if (e instanceof Http2StreamException) {
|
|
||||||
processStreamError(ctx, (Http2StreamException) e);
|
|
||||||
} else {
|
|
||||||
processConnectionError(ctx, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processConnectionError(ChannelHandlerContext ctx, Http2Exception cause) {
|
|
||||||
connection.sendGoAway(ctx, ctx.newPromise(), cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processStreamError(ChannelHandlerContext ctx, Http2StreamException cause) {
|
|
||||||
// Close the stream if it was open.
|
|
||||||
int streamId = cause.getStreamId();
|
|
||||||
ChannelPromise promise = ctx.newPromise();
|
|
||||||
Http2Stream stream = connection.getStream(streamId);
|
|
||||||
if (stream != null) {
|
|
||||||
stream.close(ctx, promise);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the Rst frame to the remote endpoint.
|
@Override
|
||||||
Http2RstStreamFrame frame = new DefaultHttp2RstStreamFrame.Builder().setStreamId(streamId)
|
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||||
.setErrorCode(cause.getError().getCode()).build();
|
// Avoid NotYetConnectedException
|
||||||
ctx.writeAndFlush(frame, promise);
|
if (!ctx.channel().isActive()) {
|
||||||
}
|
ctx.close(promise);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
private void handleInboundData(final ChannelHandlerContext ctx, Http2DataFrame frame)
|
connection.sendGoAway(ctx, promise, null);
|
||||||
throws Http2Exception {
|
|
||||||
|
|
||||||
// Check if we received a data frame for a stream which is half-closed
|
|
||||||
Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
|
|
||||||
stream.verifyState(STREAM_CLOSED, OPEN, HALF_CLOSED_LOCAL);
|
|
||||||
|
|
||||||
// Apply flow control.
|
|
||||||
inboundFlow.applyInboundFlowControl(frame, new InboundFlowController.FrameWriter() {
|
|
||||||
@Override
|
|
||||||
public void writeFrame(Http2WindowUpdateFrame frame) {
|
|
||||||
ctx.writeAndFlush(frame);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isInboundStreamAfterGoAway(frame)) {
|
|
||||||
// Ignore frames for any stream created after we sent a go-away.
|
|
||||||
frame.release();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frame.isEndOfStream()) {
|
@Override
|
||||||
stream.closeRemoteSide(ctx, ctx.newSucceededFuture());
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
for (Http2Stream stream : connection.getActiveStreams()) {
|
||||||
|
stream.close(ctx, ctx.newSucceededFuture());
|
||||||
|
}
|
||||||
|
ctx.fireChannelInactive();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow this frame to continue other handlers.
|
/**
|
||||||
ctx.fireChannelRead(frame);
|
* Handles {@link Http2Exception} objects that were thrown from other handlers. Ignores all other
|
||||||
}
|
* exceptions.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
|
if (cause instanceof Http2Exception) {
|
||||||
|
processHttp2Exception(ctx, (Http2Exception) cause);
|
||||||
|
}
|
||||||
|
|
||||||
private void handleInboundHeaders(ChannelHandlerContext ctx, Http2HeadersFrame frame)
|
ctx.fireExceptionCaught(cause);
|
||||||
throws Http2Exception {
|
|
||||||
if (isInboundStreamAfterGoAway(frame)) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int streamId = frame.getStreamId();
|
@Override
|
||||||
Http2Stream stream = connection.getStream(streamId);
|
public void channelRead(ChannelHandlerContext ctx, Object inMsg) throws Exception {
|
||||||
if (stream == null) {
|
try {
|
||||||
// Create the new stream.
|
if (inMsg instanceof Http2DataFrame) {
|
||||||
connection.remote().createStream(frame.getStreamId(), frame.getPriority(),
|
handleInboundData(ctx, (Http2DataFrame) inMsg);
|
||||||
frame.isEndOfStream());
|
} else if (inMsg instanceof Http2HeadersFrame) {
|
||||||
} else {
|
handleInboundHeaders(ctx, (Http2HeadersFrame) inMsg);
|
||||||
// If the stream already exists, it must be a reserved push stream. If so, open
|
} else if (inMsg instanceof Http2PushPromiseFrame) {
|
||||||
// it for push to the local endpoint.
|
handleInboundPushPromise(ctx, (Http2PushPromiseFrame) inMsg);
|
||||||
stream.verifyState(PROTOCOL_ERROR, RESERVED_REMOTE);
|
} else if (inMsg instanceof Http2PriorityFrame) {
|
||||||
stream.openForPush();
|
handleInboundPriority(ctx, (Http2PriorityFrame) inMsg);
|
||||||
|
} else if (inMsg instanceof Http2RstStreamFrame) {
|
||||||
// If the headers completes this stream, close it.
|
handleInboundRstStream(ctx, (Http2RstStreamFrame) inMsg);
|
||||||
if (frame.isEndOfStream()) {
|
} else if (inMsg instanceof Http2PingFrame) {
|
||||||
stream.closeRemoteSide(ctx, ctx.newSucceededFuture());
|
handleInboundPing(ctx, (Http2PingFrame) inMsg);
|
||||||
}
|
} else if (inMsg instanceof Http2GoAwayFrame) {
|
||||||
}
|
handleInboundGoAway(ctx, (Http2GoAwayFrame) inMsg);
|
||||||
|
} else if (inMsg instanceof Http2WindowUpdateFrame) {
|
||||||
ctx.fireChannelRead(frame);
|
handleInboundWindowUpdate(ctx, (Http2WindowUpdateFrame) inMsg);
|
||||||
}
|
} else if (inMsg instanceof Http2SettingsFrame) {
|
||||||
|
handleInboundSettings(ctx, (Http2SettingsFrame) inMsg);
|
||||||
private void handleInboundPushPromise(ChannelHandlerContext ctx, Http2PushPromiseFrame frame)
|
} else {
|
||||||
throws Http2Exception {
|
ctx.fireChannelRead(inMsg);
|
||||||
if (isInboundStreamAfterGoAway(frame)) {
|
}
|
||||||
// Ignore frames for any stream created after we sent a go-away.
|
|
||||||
return;
|
} catch (Http2Exception e) {
|
||||||
}
|
ReferenceCountUtil.release(inMsg);
|
||||||
|
processHttp2Exception(ctx, e);
|
||||||
// Reserve the push stream based with a priority based on the current stream's priority.
|
}
|
||||||
Http2Stream parentStream = connection.getStreamOrFail(frame.getStreamId());
|
}
|
||||||
connection.remote().reservePushStream(frame.getPromisedStreamId(), parentStream);
|
|
||||||
|
@Override
|
||||||
ctx.fireChannelRead(frame);
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
|
||||||
}
|
throws Exception {
|
||||||
|
try {
|
||||||
private void handleInboundPriority(ChannelHandlerContext ctx, Http2PriorityFrame frame)
|
if (msg instanceof Http2DataFrame) {
|
||||||
throws Http2Exception {
|
handleOutboundData(ctx, (Http2DataFrame) msg, promise);
|
||||||
if (isInboundStreamAfterGoAway(frame)) {
|
} else if (msg instanceof Http2HeadersFrame) {
|
||||||
// Ignore frames for any stream created after we sent a go-away.
|
handleOutboundHeaders(ctx, (Http2HeadersFrame) msg, promise);
|
||||||
return;
|
} else if (msg instanceof Http2PushPromiseFrame) {
|
||||||
}
|
handleOutboundPushPromise(ctx, (Http2PushPromiseFrame) msg, promise);
|
||||||
|
} else if (msg instanceof Http2PriorityFrame) {
|
||||||
Http2Stream stream = connection.getStream(frame.getStreamId());
|
handleOutboundPriority(ctx, (Http2PriorityFrame) msg, promise);
|
||||||
if (stream == null) {
|
} else if (msg instanceof Http2RstStreamFrame) {
|
||||||
// Priority frames must be ignored for closed streams.
|
handleOutboundRstStream(ctx, (Http2RstStreamFrame) msg, promise);
|
||||||
return;
|
} else if (msg instanceof Http2PingFrame) {
|
||||||
}
|
handleOutboundPing(ctx, (Http2PingFrame) msg, promise);
|
||||||
|
} else if (msg instanceof Http2GoAwayFrame) {
|
||||||
stream.verifyState(PROTOCOL_ERROR, HALF_CLOSED_LOCAL, HALF_CLOSED_REMOTE, OPEN, RESERVED_LOCAL);
|
handleOutboundGoAway();
|
||||||
|
} else if (msg instanceof Http2WindowUpdateFrame) {
|
||||||
// Set the priority on the frame.
|
handleOutboundWindowUpdate();
|
||||||
stream.setPriority(frame.getPriority());
|
} else if (msg instanceof Http2SettingsFrame) {
|
||||||
|
handleOutboundSettings(ctx, (Http2SettingsFrame) msg, promise);
|
||||||
ctx.fireChannelRead(frame);
|
} else {
|
||||||
}
|
ctx.write(msg, promise);
|
||||||
|
return;
|
||||||
private void handleInboundWindowUpdate(ChannelHandlerContext ctx, Http2WindowUpdateFrame frame)
|
}
|
||||||
throws Http2Exception {
|
|
||||||
if (isInboundStreamAfterGoAway(frame)) {
|
} catch (Throwable e) {
|
||||||
// Ignore frames for any stream created after we sent a go-away.
|
ReferenceCountUtil.release(msg);
|
||||||
return;
|
promise.setFailure(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
int streamId = frame.getStreamId();
|
|
||||||
if (streamId > 0) {
|
/**
|
||||||
Http2Stream stream = connection.getStream(streamId);
|
* Processes the given exception. Depending on the type of exception, delegates to either
|
||||||
if (stream == null) {
|
* {@link #processConnectionError} or {@link #processStreamError}.
|
||||||
// Window Update frames must be ignored for closed streams.
|
*/
|
||||||
return;
|
private void processHttp2Exception(ChannelHandlerContext ctx, Http2Exception e) {
|
||||||
}
|
if (e instanceof Http2StreamException) {
|
||||||
stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE);
|
processStreamError(ctx, (Http2StreamException) e);
|
||||||
}
|
} else {
|
||||||
|
processConnectionError(ctx, e);
|
||||||
// Update the outbound flow controller.
|
}
|
||||||
outboundFlow.updateOutboundWindowSize(streamId, frame.getWindowSizeIncrement());
|
}
|
||||||
|
|
||||||
ctx.fireChannelRead(frame);
|
private void processConnectionError(ChannelHandlerContext ctx, Http2Exception cause) {
|
||||||
}
|
connection.sendGoAway(ctx, ctx.newPromise(), cause);
|
||||||
|
}
|
||||||
private void handleInboundRstStream(ChannelHandlerContext ctx, Http2RstStreamFrame frame) {
|
|
||||||
if (isInboundStreamAfterGoAway(frame)) {
|
private void processStreamError(ChannelHandlerContext ctx, Http2StreamException cause) {
|
||||||
// Ignore frames for any stream created after we sent a go-away.
|
// Close the stream if it was open.
|
||||||
return;
|
int streamId = cause.getStreamId();
|
||||||
}
|
ChannelPromise promise = ctx.newPromise();
|
||||||
|
Http2Stream stream = connection.getStream(streamId);
|
||||||
Http2Stream stream = connection.getStream(frame.getStreamId());
|
if (stream != null) {
|
||||||
if (stream == null) {
|
stream.close(ctx, promise);
|
||||||
// RstStream frames must be ignored for closed streams.
|
}
|
||||||
return;
|
|
||||||
}
|
// Send the Rst frame to the remote endpoint.
|
||||||
|
Http2RstStreamFrame frame = new DefaultHttp2RstStreamFrame.Builder().setStreamId(streamId)
|
||||||
stream.close(ctx, ctx.newSucceededFuture());
|
.setErrorCode(cause.getError().getCode()).build();
|
||||||
|
ctx.writeAndFlush(frame, promise);
|
||||||
ctx.fireChannelRead(frame);
|
}
|
||||||
}
|
|
||||||
|
private void handleInboundData(final ChannelHandlerContext ctx, Http2DataFrame frame)
|
||||||
private void handleInboundPing(ChannelHandlerContext ctx, Http2PingFrame frame) {
|
throws Http2Exception {
|
||||||
if (frame.isAck()) {
|
|
||||||
// The remote enpoint is responding to an Ack that we sent.
|
// Check if we received a data frame for a stream which is half-closed
|
||||||
ctx.fireChannelRead(frame);
|
Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
|
||||||
return;
|
stream.verifyState(STREAM_CLOSED, OPEN, HALF_CLOSED_LOCAL);
|
||||||
}
|
|
||||||
|
// Apply flow control.
|
||||||
// The remote endpoint is sending the ping. Acknowledge receipt.
|
inboundFlow.applyInboundFlowControl(frame, new InboundFlowController.FrameWriter() {
|
||||||
DefaultHttp2PingFrame ack = new DefaultHttp2PingFrame.Builder().setAck(true)
|
@Override
|
||||||
.setData(frame.content().duplicate().retain()).build();
|
public void writeFrame(Http2WindowUpdateFrame frame) {
|
||||||
ctx.writeAndFlush(ack);
|
ctx.writeAndFlush(frame);
|
||||||
}
|
|
||||||
|
|
||||||
private void handleInboundSettings(ChannelHandlerContext ctx, Http2SettingsFrame frame)
|
|
||||||
throws Http2Exception {
|
|
||||||
if (frame.isAck()) {
|
|
||||||
// The remote endpoint is acknowledging the settings - fire this up to the next
|
|
||||||
// handler.
|
|
||||||
ctx.fireChannelRead(frame);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's not an ack, apply the settings.
|
|
||||||
if (frame.getHeaderTableSize() != null) {
|
|
||||||
// TODO(nathanmittler): what's the right thing handle this?
|
|
||||||
// headersEncoder.setHeaderTableSize(frame.getHeaderTableSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame.getPushEnabled() != null) {
|
|
||||||
connection.remote().setPushToAllowed(frame.getPushEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame.getMaxConcurrentStreams() != null) {
|
|
||||||
int value = Math.max(0, (int) Math.min(Integer.MAX_VALUE, frame.getMaxConcurrentStreams()));
|
|
||||||
connection.local().setMaxStreams(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame.getInitialWindowSize() != null) {
|
|
||||||
outboundFlow.setInitialOutboundWindowSize(frame.getInitialWindowSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Acknowledge receipt of the settings.
|
|
||||||
Http2Frame ack = new DefaultHttp2SettingsFrame.Builder().setAck(true).build();
|
|
||||||
ctx.writeAndFlush(ack);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleInboundGoAway(ChannelHandlerContext ctx, Http2GoAwayFrame frame) {
|
|
||||||
// Don't allow any more connections to be created.
|
|
||||||
connection.goAwayReceived();
|
|
||||||
ctx.fireChannelRead(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether or not the stream was created after we sent a go-away frame. Frames from
|
|
||||||
* streams created after we sent a go-away should be ignored. Frames for the connection stream ID
|
|
||||||
* (i.e. 0) will always be allowed.
|
|
||||||
*/
|
|
||||||
private boolean isInboundStreamAfterGoAway(Http2StreamFrame frame) {
|
|
||||||
return connection.isGoAwaySent()
|
|
||||||
&& connection.remote().getLastStreamCreated() <= frame.getStreamId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleOutboundData(final ChannelHandlerContext ctx, Http2DataFrame frame,
|
|
||||||
final ChannelPromise promise) throws Http2Exception {
|
|
||||||
if (connection.isGoAway()) {
|
|
||||||
throw format(PROTOCOL_ERROR, "Sending data after connection going away.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
|
|
||||||
stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE);
|
|
||||||
|
|
||||||
// Hand control of the frame to the flow controller.
|
|
||||||
outboundFlow.sendFlowControlled(frame, new OutboundFlowController.FrameWriter() {
|
|
||||||
@Override
|
|
||||||
public void writeFrame(Http2DataFrame frame) {
|
|
||||||
ChannelFuture future = ctx.writeAndFlush(frame, promise);
|
|
||||||
|
|
||||||
// Close the connection on write failures that leave the outbound flow control
|
|
||||||
// window in a corrupt state.
|
|
||||||
future.addListener(new ChannelFutureListener() {
|
|
||||||
@Override
|
|
||||||
public void operationComplete(ChannelFuture future) throws Exception {
|
|
||||||
if (!future.isSuccess()) {
|
|
||||||
processHttp2Exception(ctx, toHttp2Exception(future.cause()));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close the local side of the stream if this is the last frame
|
if (isInboundStreamAfterGoAway(frame)) {
|
||||||
if (frame.isEndOfStream()) {
|
// Ignore frames for any stream created after we sent a go-away.
|
||||||
Http2Stream stream = connection.getStream(frame.getStreamId());
|
frame.release();
|
||||||
stream.closeLocalSide(ctx, promise);
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
if (frame.isEndOfStream()) {
|
||||||
public void setFailure(Throwable cause) {
|
stream.closeRemoteSide(ctx, ctx.newSucceededFuture());
|
||||||
promise.setFailure(cause);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleOutboundHeaders(ChannelHandlerContext ctx, Http2HeadersFrame frame,
|
// Allow this frame to continue other handlers.
|
||||||
ChannelPromise promise) throws Http2Exception {
|
ctx.fireChannelRead(frame);
|
||||||
if (connection.isGoAway()) {
|
|
||||||
throw format(PROTOCOL_ERROR, "Sending headers after connection going away.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Http2Stream stream = connection.getStream(frame.getStreamId());
|
private void handleInboundHeaders(ChannelHandlerContext ctx, Http2HeadersFrame frame)
|
||||||
if (stream == null) {
|
throws Http2Exception {
|
||||||
// Creates a new locally-initiated stream.
|
if (isInboundStreamAfterGoAway(frame)) {
|
||||||
stream = connection.local().createStream(frame.getStreamId(), frame.getPriority(),
|
return;
|
||||||
frame.isEndOfStream());
|
}
|
||||||
} else {
|
|
||||||
// If the stream already exists, it must be a reserved push stream. If so, open
|
|
||||||
// it for push to the remote endpoint.
|
|
||||||
stream.verifyState(PROTOCOL_ERROR, RESERVED_LOCAL);
|
|
||||||
stream.openForPush();
|
|
||||||
|
|
||||||
// If the headers are the end of the stream, close it now.
|
int streamId = frame.getStreamId();
|
||||||
if (frame.isEndOfStream()) {
|
Http2Stream stream = connection.getStream(streamId);
|
||||||
stream.closeLocalSide(ctx, promise);
|
if (stream == null) {
|
||||||
}
|
// Create the new stream.
|
||||||
|
connection.remote().createStream(frame.getStreamId(), frame.getPriority(),
|
||||||
|
frame.isEndOfStream());
|
||||||
|
} else {
|
||||||
|
// If the stream already exists, it must be a reserved push stream. If so, open
|
||||||
|
// it for push to the local endpoint.
|
||||||
|
stream.verifyState(PROTOCOL_ERROR, RESERVED_REMOTE);
|
||||||
|
stream.openForPush();
|
||||||
|
|
||||||
|
// If the headers completes this stream, close it.
|
||||||
|
if (frame.isEndOfStream()) {
|
||||||
|
stream.closeRemoteSide(ctx, ctx.newSucceededFuture());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fireChannelRead(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush to send all of the frames.
|
private void handleInboundPushPromise(ChannelHandlerContext ctx, Http2PushPromiseFrame frame)
|
||||||
ctx.writeAndFlush(frame, promise);
|
throws Http2Exception {
|
||||||
}
|
if (isInboundStreamAfterGoAway(frame)) {
|
||||||
|
// Ignore frames for any stream created after we sent a go-away.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
private void handleOutboundPushPromise(ChannelHandlerContext ctx, Http2PushPromiseFrame frame,
|
// Reserve the push stream based with a priority based on the current stream's priority.
|
||||||
ChannelPromise promise) throws Http2Exception {
|
Http2Stream parentStream = connection.getStreamOrFail(frame.getStreamId());
|
||||||
if (connection.isGoAway()) {
|
connection.remote().reservePushStream(frame.getPromisedStreamId(), parentStream);
|
||||||
throw format(PROTOCOL_ERROR, "Sending push promise after connection going away.");
|
|
||||||
|
ctx.fireChannelRead(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserve the promised stream.
|
private void handleInboundPriority(ChannelHandlerContext ctx, Http2PriorityFrame frame)
|
||||||
Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
|
throws Http2Exception {
|
||||||
connection.local().reservePushStream(frame.getPromisedStreamId(), stream);
|
if (isInboundStreamAfterGoAway(frame)) {
|
||||||
|
// Ignore frames for any stream created after we sent a go-away.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Write the frame.
|
Http2Stream stream = connection.getStream(frame.getStreamId());
|
||||||
ctx.writeAndFlush(frame, promise);
|
if (stream == null) {
|
||||||
}
|
// Priority frames must be ignored for closed streams.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
private void handleOutboundPriority(ChannelHandlerContext ctx, Http2PriorityFrame frame,
|
stream.verifyState(PROTOCOL_ERROR, HALF_CLOSED_LOCAL, HALF_CLOSED_REMOTE, OPEN, RESERVED_LOCAL);
|
||||||
ChannelPromise promise) throws Http2Exception {
|
|
||||||
if (connection.isGoAway()) {
|
// Set the priority on the frame.
|
||||||
throw format(PROTOCOL_ERROR, "Sending priority after connection going away.");
|
stream.setPriority(frame.getPriority());
|
||||||
|
|
||||||
|
ctx.fireChannelRead(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the priority on the stream and forward the frame.
|
private void handleInboundWindowUpdate(ChannelHandlerContext ctx, Http2WindowUpdateFrame frame)
|
||||||
Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
|
throws Http2Exception {
|
||||||
stream.setPriority(frame.getPriority());
|
if (isInboundStreamAfterGoAway(frame)) {
|
||||||
ctx.writeAndFlush(frame, promise);
|
// Ignore frames for any stream created after we sent a go-away.
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
private void handleOutboundRstStream(ChannelHandlerContext ctx, Http2RstStreamFrame frame,
|
int streamId = frame.getStreamId();
|
||||||
ChannelPromise promise) {
|
if (streamId > 0) {
|
||||||
Http2Stream stream = connection.getStream(frame.getStreamId());
|
Http2Stream stream = connection.getStream(streamId);
|
||||||
if (stream == null) {
|
if (stream == null) {
|
||||||
// The stream may already have been closed ... ignore.
|
// Window Update frames must be ignored for closed streams.
|
||||||
promise.setSuccess();
|
return;
|
||||||
return;
|
}
|
||||||
|
stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the outbound flow controller.
|
||||||
|
outboundFlow.updateOutboundWindowSize(streamId, frame.getWindowSizeIncrement());
|
||||||
|
|
||||||
|
ctx.fireChannelRead(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.close(ctx, promise);
|
private void handleInboundRstStream(ChannelHandlerContext ctx, Http2RstStreamFrame frame) {
|
||||||
ctx.writeAndFlush(frame, promise);
|
if (isInboundStreamAfterGoAway(frame)) {
|
||||||
}
|
// Ignore frames for any stream created after we sent a go-away.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
private void handleOutboundPing(ChannelHandlerContext ctx, Http2PingFrame frame,
|
Http2Stream stream = connection.getStream(frame.getStreamId());
|
||||||
ChannelPromise promise) throws Http2Exception {
|
if (stream == null) {
|
||||||
if (connection.isGoAway()) {
|
// RstStream frames must be ignored for closed streams.
|
||||||
throw format(PROTOCOL_ERROR, "Sending ping after connection going away.");
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.close(ctx, ctx.newSucceededFuture());
|
||||||
|
|
||||||
|
ctx.fireChannelRead(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frame.isAck()) {
|
private void handleInboundPing(ChannelHandlerContext ctx, Http2PingFrame frame) {
|
||||||
throw format(PROTOCOL_ERROR, "Another handler attempting to send ping ack");
|
if (frame.isAck()) {
|
||||||
|
// The remote enpoint is responding to an Ack that we sent.
|
||||||
|
ctx.fireChannelRead(frame);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The remote endpoint is sending the ping. Acknowledge receipt.
|
||||||
|
DefaultHttp2PingFrame ack = new DefaultHttp2PingFrame.Builder().setAck(true)
|
||||||
|
.setData(frame.content().duplicate().retain()).build();
|
||||||
|
ctx.writeAndFlush(ack);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just pass the frame through.
|
private void handleInboundSettings(ChannelHandlerContext ctx, Http2SettingsFrame frame)
|
||||||
ctx.writeAndFlush(frame, promise);
|
throws Http2Exception {
|
||||||
}
|
if (frame.isAck()) {
|
||||||
|
// The remote endpoint is acknowledging the settings - fire this up to the next
|
||||||
|
// handler.
|
||||||
|
ctx.fireChannelRead(frame);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
private void handleOutboundGoAway() throws Http2Exception {
|
// It's not an ack, apply the settings.
|
||||||
// Why is this being sent? Intercept it and fail the write.
|
if (frame.getHeaderTableSize() != null) {
|
||||||
// Should have sent a CLOSE ChannelStateEvent
|
// TODO(nathanmittler): what's the right thing handle this?
|
||||||
throw format(PROTOCOL_ERROR, "Another handler attempted to send GoAway.");
|
// headersEncoder.setHeaderTableSize(frame.getHeaderTableSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleOutboundWindowUpdate() throws Http2Exception {
|
if (frame.getPushEnabled() != null) {
|
||||||
// Why is this being sent? Intercept it and fail the write.
|
connection.remote().setPushToAllowed(frame.getPushEnabled());
|
||||||
throw format(PROTOCOL_ERROR, "Another handler attempted to send window update.");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void handleOutboundSettings(ChannelHandlerContext ctx, Http2SettingsFrame frame,
|
if (frame.getMaxConcurrentStreams() != null) {
|
||||||
ChannelPromise promise) throws Http2Exception {
|
int value = Math.max(0, (int) Math.min(Integer.MAX_VALUE, frame.getMaxConcurrentStreams()));
|
||||||
if (connection.isGoAway()) {
|
connection.local().setMaxStreams(value);
|
||||||
throw format(PROTOCOL_ERROR, "Sending settings after connection going away.");
|
}
|
||||||
|
|
||||||
|
if (frame.getInitialWindowSize() != null) {
|
||||||
|
outboundFlow.setInitialOutboundWindowSize(frame.getInitialWindowSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acknowledge receipt of the settings.
|
||||||
|
Http2Frame ack = new DefaultHttp2SettingsFrame.Builder().setAck(true).build();
|
||||||
|
ctx.writeAndFlush(ack);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frame.isAck()) {
|
private void handleInboundGoAway(ChannelHandlerContext ctx, Http2GoAwayFrame frame) {
|
||||||
throw format(PROTOCOL_ERROR, "Another handler attempting to send settings ack");
|
// Don't allow any more connections to be created.
|
||||||
|
connection.goAwayReceived();
|
||||||
|
ctx.fireChannelRead(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frame.getPushEnabled() != null) {
|
/**
|
||||||
// Enable/disable server push to this endpoint.
|
* Determines whether or not the stream was created after we sent a go-away frame. Frames from
|
||||||
connection.local().setPushToAllowed(frame.getPushEnabled());
|
* streams created after we sent a go-away should be ignored. Frames for the connection stream ID
|
||||||
|
* (i.e. 0) will always be allowed.
|
||||||
|
*/
|
||||||
|
private boolean isInboundStreamAfterGoAway(Http2StreamFrame frame) {
|
||||||
|
return connection.isGoAwaySent()
|
||||||
|
&& connection.remote().getLastStreamCreated() <= frame.getStreamId();
|
||||||
}
|
}
|
||||||
if (frame.getHeaderTableSize() != null) {
|
|
||||||
// TODO(nathanmittler): what's the right way to handle this?
|
private void handleOutboundData(final ChannelHandlerContext ctx, Http2DataFrame frame,
|
||||||
// headersDecoder.setHeaderTableSize(frame.getHeaderTableSize());
|
final ChannelPromise promise) throws Http2Exception {
|
||||||
|
if (connection.isGoAway()) {
|
||||||
|
throw format(PROTOCOL_ERROR, "Sending data after connection going away.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
|
||||||
|
stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE);
|
||||||
|
|
||||||
|
// Hand control of the frame to the flow controller.
|
||||||
|
outboundFlow.sendFlowControlled(frame, new OutboundFlowController.FrameWriter() {
|
||||||
|
@Override
|
||||||
|
public void writeFrame(Http2DataFrame frame) {
|
||||||
|
ChannelFuture future = ctx.writeAndFlush(frame, promise);
|
||||||
|
|
||||||
|
// Close the connection on write failures that leave the outbound flow control
|
||||||
|
// window in a corrupt state.
|
||||||
|
future.addListener(new ChannelFutureListener() {
|
||||||
|
@Override
|
||||||
|
public void operationComplete(ChannelFuture future) throws Exception {
|
||||||
|
if (!future.isSuccess()) {
|
||||||
|
processHttp2Exception(ctx, toHttp2Exception(future.cause()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close the local side of the stream if this is the last frame
|
||||||
|
if (frame.isEndOfStream()) {
|
||||||
|
Http2Stream stream = connection.getStream(frame.getStreamId());
|
||||||
|
stream.closeLocalSide(ctx, promise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFailure(Throwable cause) {
|
||||||
|
promise.setFailure(cause);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (frame.getMaxConcurrentStreams() != null) {
|
|
||||||
// Update maximum number of streams the remote endpoint can initiate.
|
private void handleOutboundHeaders(ChannelHandlerContext ctx, Http2HeadersFrame frame,
|
||||||
if (frame.getMaxConcurrentStreams() < 0L
|
ChannelPromise promise) throws Http2Exception {
|
||||||
|| frame.getMaxConcurrentStreams() > Integer.MAX_VALUE) {
|
if (connection.isGoAway()) {
|
||||||
throw format(PROTOCOL_ERROR, "Invalid value for max concurrent streams: %d",
|
throw format(PROTOCOL_ERROR, "Sending headers after connection going away.");
|
||||||
frame.getMaxConcurrentStreams());
|
}
|
||||||
}
|
|
||||||
connection.remote().setMaxStreams(frame.getMaxConcurrentStreams().intValue());
|
Http2Stream stream = connection.getStream(frame.getStreamId());
|
||||||
|
if (stream == null) {
|
||||||
|
// Creates a new locally-initiated stream.
|
||||||
|
stream = connection.local().createStream(frame.getStreamId(), frame.getPriority(),
|
||||||
|
frame.isEndOfStream());
|
||||||
|
} else {
|
||||||
|
// If the stream already exists, it must be a reserved push stream. If so, open
|
||||||
|
// it for push to the remote endpoint.
|
||||||
|
stream.verifyState(PROTOCOL_ERROR, RESERVED_LOCAL);
|
||||||
|
stream.openForPush();
|
||||||
|
|
||||||
|
// If the headers are the end of the stream, close it now.
|
||||||
|
if (frame.isEndOfStream()) {
|
||||||
|
stream.closeLocalSide(ctx, promise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush to send all of the frames.
|
||||||
|
ctx.writeAndFlush(frame, promise);
|
||||||
}
|
}
|
||||||
if (frame.getInitialWindowSize() != null) {
|
|
||||||
// Update the initial window size for inbound traffic.
|
private void handleOutboundPushPromise(ChannelHandlerContext ctx, Http2PushPromiseFrame frame,
|
||||||
if (frame.getInitialWindowSize() < 0) {
|
ChannelPromise promise) throws Http2Exception {
|
||||||
throw format(PROTOCOL_ERROR, "Invalid value for initial window size: %d",
|
if (connection.isGoAway()) {
|
||||||
frame.getInitialWindowSize());
|
throw format(PROTOCOL_ERROR, "Sending push promise after connection going away.");
|
||||||
}
|
}
|
||||||
inboundFlow.setInitialInboundWindowSize(frame.getInitialWindowSize());
|
|
||||||
|
// Reserve the promised stream.
|
||||||
|
Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
|
||||||
|
connection.local().reservePushStream(frame.getPromisedStreamId(), stream);
|
||||||
|
|
||||||
|
// Write the frame.
|
||||||
|
ctx.writeAndFlush(frame, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleOutboundPriority(ChannelHandlerContext ctx, Http2PriorityFrame frame,
|
||||||
|
ChannelPromise promise) throws Http2Exception {
|
||||||
|
if (connection.isGoAway()) {
|
||||||
|
throw format(PROTOCOL_ERROR, "Sending priority after connection going away.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the priority on the stream and forward the frame.
|
||||||
|
Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
|
||||||
|
stream.setPriority(frame.getPriority());
|
||||||
|
ctx.writeAndFlush(frame, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleOutboundRstStream(ChannelHandlerContext ctx, Http2RstStreamFrame frame,
|
||||||
|
ChannelPromise promise) {
|
||||||
|
Http2Stream stream = connection.getStream(frame.getStreamId());
|
||||||
|
if (stream == null) {
|
||||||
|
// The stream may already have been closed ... ignore.
|
||||||
|
promise.setSuccess();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.close(ctx, promise);
|
||||||
|
ctx.writeAndFlush(frame, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleOutboundPing(ChannelHandlerContext ctx, Http2PingFrame frame,
|
||||||
|
ChannelPromise promise) throws Http2Exception {
|
||||||
|
if (connection.isGoAway()) {
|
||||||
|
throw format(PROTOCOL_ERROR, "Sending ping after connection going away.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame.isAck()) {
|
||||||
|
throw format(PROTOCOL_ERROR, "Another handler attempting to send ping ack");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just pass the frame through.
|
||||||
|
ctx.writeAndFlush(frame, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleOutboundGoAway() throws Http2Exception {
|
||||||
|
// Why is this being sent? Intercept it and fail the write.
|
||||||
|
// Should have sent a CLOSE ChannelStateEvent
|
||||||
|
throw format(PROTOCOL_ERROR, "Another handler attempted to send GoAway.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleOutboundWindowUpdate() throws Http2Exception {
|
||||||
|
// Why is this being sent? Intercept it and fail the write.
|
||||||
|
throw format(PROTOCOL_ERROR, "Another handler attempted to send window update.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleOutboundSettings(ChannelHandlerContext ctx, Http2SettingsFrame frame,
|
||||||
|
ChannelPromise promise) throws Http2Exception {
|
||||||
|
if (connection.isGoAway()) {
|
||||||
|
throw format(PROTOCOL_ERROR, "Sending settings after connection going away.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame.isAck()) {
|
||||||
|
throw format(PROTOCOL_ERROR, "Another handler attempting to send settings ack");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame.getPushEnabled() != null) {
|
||||||
|
// Enable/disable server push to this endpoint.
|
||||||
|
connection.local().setPushToAllowed(frame.getPushEnabled());
|
||||||
|
}
|
||||||
|
if (frame.getHeaderTableSize() != null) {
|
||||||
|
// TODO(nathanmittler): what's the right way to handle this?
|
||||||
|
// headersDecoder.setHeaderTableSize(frame.getHeaderTableSize());
|
||||||
|
}
|
||||||
|
if (frame.getMaxConcurrentStreams() != null) {
|
||||||
|
// Update maximum number of streams the remote endpoint can initiate.
|
||||||
|
if (frame.getMaxConcurrentStreams() < 0L
|
||||||
|
|| frame.getMaxConcurrentStreams() > Integer.MAX_VALUE) {
|
||||||
|
throw format(PROTOCOL_ERROR, "Invalid value for max concurrent streams: %d",
|
||||||
|
frame.getMaxConcurrentStreams());
|
||||||
|
}
|
||||||
|
connection.remote().setMaxStreams(frame.getMaxConcurrentStreams().intValue());
|
||||||
|
}
|
||||||
|
if (frame.getInitialWindowSize() != null) {
|
||||||
|
// Update the initial window size for inbound traffic.
|
||||||
|
if (frame.getInitialWindowSize() < 0) {
|
||||||
|
throw format(PROTOCOL_ERROR, "Invalid value for initial window size: %d",
|
||||||
|
frame.getInitialWindowSize());
|
||||||
|
}
|
||||||
|
inboundFlow.setInitialInboundWindowSize(frame.getInitialWindowSize());
|
||||||
|
}
|
||||||
|
ctx.writeAndFlush(frame, promise);
|
||||||
}
|
}
|
||||||
ctx.writeAndFlush(frame, promise);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package io.netty.handler.codec.http2.draft10.connection;
|
|||||||
|
|
||||||
import static io.netty.handler.codec.http2.draft10.Http2Error.INTERNAL_ERROR;
|
import static io.netty.handler.codec.http2.draft10.Http2Error.INTERNAL_ERROR;
|
||||||
import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
|
import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
@ -27,36 +28,36 @@ import io.netty.handler.codec.http2.draft10.Http2Exception;
|
|||||||
*/
|
*/
|
||||||
public final class Http2ConnectionUtil {
|
public final class Http2ConnectionUtil {
|
||||||
|
|
||||||
public static final int DEFAULT_FLOW_CONTROL_WINDOW_SIZE = 65535;
|
public static final int DEFAULT_FLOW_CONTROL_WINDOW_SIZE = 65535;
|
||||||
public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
|
public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
|
||||||
public static final int DEFAULT_MAX_HEADER_SIZE = 4096;
|
public static final int DEFAULT_MAX_HEADER_SIZE = 4096;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given cause to a {@link Http2Exception} if it isn't already.
|
* Converts the given cause to a {@link Http2Exception} if it isn't already.
|
||||||
*/
|
*/
|
||||||
public static Http2Exception toHttp2Exception(Throwable cause) {
|
public static Http2Exception toHttp2Exception(Throwable cause) {
|
||||||
if (cause instanceof Http2Exception) {
|
if (cause instanceof Http2Exception) {
|
||||||
return (Http2Exception) cause;
|
return (Http2Exception) cause;
|
||||||
|
}
|
||||||
|
String msg = cause != null ? cause.getMessage() : "Failed writing the data frame.";
|
||||||
|
return format(INTERNAL_ERROR, msg);
|
||||||
}
|
}
|
||||||
String msg = cause != null ? cause.getMessage() : "Failed writing the data frame.";
|
|
||||||
return format(INTERNAL_ERROR, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a buffer containing the error message from the given exception. If the cause is
|
* Creates a buffer containing the error message from the given exception. If the cause is
|
||||||
* {@code null} returns an empty buffer.
|
* {@code null} returns an empty buffer.
|
||||||
*/
|
*/
|
||||||
public static ByteBuf toByteBuf(ChannelHandlerContext ctx, Throwable cause) {
|
public static ByteBuf toByteBuf(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
ByteBuf debugData = Unpooled.EMPTY_BUFFER;
|
ByteBuf debugData = Unpooled.EMPTY_BUFFER;
|
||||||
if (cause != null) {
|
if (cause != null) {
|
||||||
// Create the debug message.
|
// Create the debug message.
|
||||||
byte[] msg = cause.getMessage().getBytes();
|
byte[] msg = cause.getMessage().getBytes();
|
||||||
debugData = ctx.alloc().buffer(msg.length);
|
debugData = ctx.alloc().buffer(msg.length);
|
||||||
debugData.writeBytes(msg);
|
debugData.writeBytes(msg);
|
||||||
|
}
|
||||||
|
return debugData;
|
||||||
}
|
}
|
||||||
return debugData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Http2ConnectionUtil() {
|
private Http2ConnectionUtil() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,71 +25,71 @@ import io.netty.handler.codec.http2.draft10.Http2Exception;
|
|||||||
*/
|
*/
|
||||||
public interface Http2Stream extends Comparable<Http2Stream> {
|
public interface Http2Stream extends Comparable<Http2Stream> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The allowed states of an HTTP2 stream.
|
* The allowed states of an HTTP2 stream.
|
||||||
*/
|
*/
|
||||||
enum State {
|
enum State {
|
||||||
IDLE, RESERVED_LOCAL, RESERVED_REMOTE, OPEN, HALF_CLOSED_LOCAL, HALF_CLOSED_REMOTE, CLOSED;
|
IDLE, RESERVED_LOCAL, RESERVED_REMOTE, OPEN, HALF_CLOSED_LOCAL, HALF_CLOSED_REMOTE, CLOSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the unique identifier for this stream within the connection.
|
* Gets the unique identifier for this stream within the connection.
|
||||||
*/
|
*/
|
||||||
int getId();
|
int getId();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the state of this stream.
|
* Gets the state of this stream.
|
||||||
*/
|
*/
|
||||||
State getState();
|
State getState();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that the stream is in one of the given allowed states.
|
* Verifies that the stream is in one of the given allowed states.
|
||||||
*/
|
*/
|
||||||
void verifyState(Http2Error error, State... allowedStates) throws Http2Exception;
|
void verifyState(Http2Error error, State... allowedStates) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the priority of this stream. A value of zero is the highest priority and a value of
|
* Sets the priority of this stream. A value of zero is the highest priority and a value of
|
||||||
* {@link Integer#MAX_VALUE} is the lowest.
|
* {@link Integer#MAX_VALUE} is the lowest.
|
||||||
*/
|
*/
|
||||||
void setPriority(int priority) throws Http2Exception;
|
void setPriority(int priority) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the priority of this stream. A value of zero is the highest priority and a value of
|
* Gets the priority of this stream. A value of zero is the highest priority and a value of
|
||||||
* {@link Integer#MAX_VALUE} is the lowest.
|
* {@link Integer#MAX_VALUE} is the lowest.
|
||||||
*/
|
*/
|
||||||
int getPriority();
|
int getPriority();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this is a reserved push stream, opens the stream for push in one direction.
|
* If this is a reserved push stream, opens the stream for push in one direction.
|
||||||
*/
|
*/
|
||||||
void openForPush() throws Http2Exception;
|
void openForPush() throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the stream.
|
* Closes the stream.
|
||||||
*/
|
*/
|
||||||
void close(ChannelHandlerContext ctx, ChannelFuture future);
|
void close(ChannelHandlerContext ctx, ChannelFuture future);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the local side of this stream. If this makes the stream closed, the child is closed as
|
* Closes the local side of this stream. If this makes the stream closed, the child is closed as
|
||||||
* well.
|
* well.
|
||||||
*/
|
*/
|
||||||
void closeLocalSide(ChannelHandlerContext ctx, ChannelFuture future);
|
void closeLocalSide(ChannelHandlerContext ctx, ChannelFuture future);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the remote side of this stream. If this makes the stream closed, the child is closed as
|
* Closes the remote side of this stream. If this makes the stream closed, the child is closed as
|
||||||
* well.
|
* well.
|
||||||
*/
|
*/
|
||||||
void closeRemoteSide(ChannelHandlerContext ctx, ChannelFuture future);
|
void closeRemoteSide(ChannelHandlerContext ctx, ChannelFuture future);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether the remote side of this stream is open (i.e. the state is either
|
* Indicates whether the remote side of this stream is open (i.e. the state is either
|
||||||
* {@link State#OPEN} or {@link State#HALF_CLOSED_LOCAL}).
|
* {@link State#OPEN} or {@link State#HALF_CLOSED_LOCAL}).
|
||||||
*/
|
*/
|
||||||
boolean isRemoteSideOpen();
|
boolean isRemoteSideOpen();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether the local side of this stream is open (i.e. the state is either
|
* Indicates whether the local side of this stream is open (i.e. the state is either
|
||||||
* {@link State#OPEN} or {@link State#HALF_CLOSED_REMOTE}).
|
* {@link State#OPEN} or {@link State#HALF_CLOSED_REMOTE}).
|
||||||
*/
|
*/
|
||||||
boolean isLocalSideOpen();
|
boolean isLocalSideOpen();
|
||||||
}
|
}
|
||||||
|
@ -24,33 +24,33 @@ import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
|
|||||||
*/
|
*/
|
||||||
public interface InboundFlowController {
|
public interface InboundFlowController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A writer of window update frames.
|
* A writer of window update frames.
|
||||||
*/
|
*/
|
||||||
interface FrameWriter {
|
interface FrameWriter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a window update frame to the remote endpoint.
|
||||||
|
*/
|
||||||
|
void writeFrame(Http2WindowUpdateFrame frame);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a window update frame to the remote endpoint.
|
* Sets the initial inbound flow control window size and updates all stream window sizes by the
|
||||||
|
* delta. This is called as part of the processing for an outbound SETTINGS frame.
|
||||||
|
*
|
||||||
|
* @param newWindowSize the new initial window size.
|
||||||
|
* @throws Http2Exception thrown if any protocol-related error occurred.
|
||||||
*/
|
*/
|
||||||
void writeFrame(Http2WindowUpdateFrame frame);
|
void setInitialInboundWindowSize(int newWindowSize) throws Http2Exception;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the initial inbound flow control window size and updates all stream window sizes by the
|
* Applies flow control for the received data frame.
|
||||||
* delta. This is called as part of the processing for an outbound SETTINGS frame.
|
*
|
||||||
*
|
* @param dataFrame the flow controlled frame
|
||||||
* @param newWindowSize the new initial window size.
|
* @param frameWriter allows this flow controller to send window updates to the remote endpoint.
|
||||||
* @throws Http2Exception thrown if any protocol-related error occurred.
|
* @throws Http2Exception thrown if any protocol-related error occurred.
|
||||||
*/
|
*/
|
||||||
void setInitialInboundWindowSize(int newWindowSize) throws Http2Exception;
|
void applyInboundFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
|
||||||
|
throws Http2Exception;
|
||||||
/**
|
|
||||||
* Applies flow control for the received data frame.
|
|
||||||
*
|
|
||||||
* @param dataFrame the flow controlled frame
|
|
||||||
* @param frameWriter allows this flow controller to send window updates to the remote endpoint.
|
|
||||||
* @throws Http2Exception thrown if any protocol-related error occurred.
|
|
||||||
*/
|
|
||||||
void applyInboundFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
|
|
||||||
throws Http2Exception;
|
|
||||||
}
|
}
|
||||||
|
@ -24,56 +24,56 @@ import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
|
|||||||
*/
|
*/
|
||||||
public interface OutboundFlowController {
|
public interface OutboundFlowController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface that abstracts the writing of {@link Http2Frame} objects to the remote endpoint.
|
* Interface that abstracts the writing of {@link Http2Frame} objects to the remote endpoint.
|
||||||
*/
|
*/
|
||||||
interface FrameWriter {
|
interface FrameWriter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a single data frame to the remote endpoint.
|
||||||
|
*/
|
||||||
|
void writeFrame(Http2DataFrame frame);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if an error occurred before the write could take place. Sets the failure on the
|
||||||
|
* channel promise.
|
||||||
|
*/
|
||||||
|
void setFailure(Throwable cause);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a single data frame to the remote endpoint.
|
* Sets the initial size of the connection's outbound flow control window. The outbound flow
|
||||||
|
* control windows for all streams are updated by the delta in the initial window size. This is
|
||||||
|
* called as part of the processing of a SETTINGS frame received from the remote endpoint.
|
||||||
|
*
|
||||||
|
* @param newWindowSize the new initial window size.
|
||||||
*/
|
*/
|
||||||
void writeFrame(Http2DataFrame frame);
|
void setInitialOutboundWindowSize(int newWindowSize) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called if an error occurred before the write could take place. Sets the failure on the
|
* Updates the size of the stream's outbound flow control window. This is called upon receiving a
|
||||||
* channel promise.
|
* WINDOW_UPDATE frame from the remote endpoint.
|
||||||
|
*
|
||||||
|
* @param streamId the ID of the stream, or zero if the window is for the entire connection.
|
||||||
|
* @param deltaWindowSize the change in size of the outbound flow control window.
|
||||||
|
* @throws Http2Exception thrown if a protocol-related error occurred.
|
||||||
*/
|
*/
|
||||||
void setFailure(Throwable cause);
|
void updateOutboundWindowSize(int streamId, int deltaWindowSize) throws Http2Exception;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the initial size of the connection's outbound flow control window. The outbound flow
|
* Sends the frame with outbound flow control applied. The frame may be written at a later time,
|
||||||
* control windows for all streams are updated by the delta in the initial window size. This is
|
* depending on whether the remote endpoint can receive the frame now.
|
||||||
* called as part of the processing of a SETTINGS frame received from the remote endpoint.
|
* <p/>
|
||||||
*
|
* Data frame flow control processing requirements:
|
||||||
* @param newWindowSize the new initial window size.
|
* <p/>
|
||||||
*/
|
* Sender must not send a data frame with data length greater than the transfer window size. After
|
||||||
void setInitialOutboundWindowSize(int newWindowSize) throws Http2Exception;
|
* sending each data frame, the stream's transfer window size is decremented by the amount of data
|
||||||
|
* transmitted. When the window size becomes less than or equal to 0, the sender must pause
|
||||||
/**
|
* transmitting data frames.
|
||||||
* Updates the size of the stream's outbound flow control window. This is called upon receiving a
|
*
|
||||||
* WINDOW_UPDATE frame from the remote endpoint.
|
* @param frame the frame to send.
|
||||||
*
|
* @param frameWriter peforms to the write of the frame to the remote endpoint.
|
||||||
* @param streamId the ID of the stream, or zero if the window is for the entire connection.
|
* @throws Http2Exception thrown if a protocol-related error occurred.
|
||||||
* @param deltaWindowSize the change in size of the outbound flow control window.
|
*/
|
||||||
* @throws Http2Exception thrown if a protocol-related error occurred.
|
void sendFlowControlled(Http2DataFrame frame, FrameWriter frameWriter) throws Http2Exception;
|
||||||
*/
|
|
||||||
void updateOutboundWindowSize(int streamId, int deltaWindowSize) throws Http2Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the frame with outbound flow control applied. The frame may be written at a later time,
|
|
||||||
* depending on whether the remote endpoint can receive the frame now.
|
|
||||||
* <p>
|
|
||||||
* Data frame flow control processing requirements:
|
|
||||||
* <p>
|
|
||||||
* Sender must not send a data frame with data length greater than the transfer window size. After
|
|
||||||
* sending each data frame, the stream's transfer window size is decremented by the amount of data
|
|
||||||
* transmitted. When the window size becomes less than or equal to 0, the sender must pause
|
|
||||||
* transmitting data frames.
|
|
||||||
*
|
|
||||||
* @param frame the frame to send.
|
|
||||||
* @param frameWriter peforms to the write of the frame to the remote endpoint.
|
|
||||||
* @throws Http2Exception thrown if a protocol-related error occurred.
|
|
||||||
*/
|
|
||||||
void sendFlowControlled(Http2DataFrame frame, FrameWriter frameWriter) throws Http2Exception;
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package io.netty.handler.codec.http2.draft10.frame;
|
|||||||
|
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_UNSIGNED_SHORT;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_UNSIGNED_SHORT;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.DefaultByteBufHolder;
|
import io.netty.buffer.DefaultByteBufHolder;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
@ -26,165 +27,165 @@ import io.netty.buffer.Unpooled;
|
|||||||
*/
|
*/
|
||||||
public final class DefaultHttp2DataFrame extends DefaultByteBufHolder implements Http2DataFrame {
|
public final class DefaultHttp2DataFrame extends DefaultByteBufHolder implements Http2DataFrame {
|
||||||
|
|
||||||
private final int paddingLength;
|
private final int paddingLength;
|
||||||
private final int streamId;
|
private final int streamId;
|
||||||
private final boolean endOfStream;
|
private final boolean endOfStream;
|
||||||
|
|
||||||
private DefaultHttp2DataFrame(Builder builder) {
|
private DefaultHttp2DataFrame(Builder builder) {
|
||||||
super(builder.content);
|
super(builder.content);
|
||||||
this.streamId = builder.streamId;
|
this.streamId = builder.streamId;
|
||||||
this.endOfStream = builder.endOfStream;
|
this.endOfStream = builder.endOfStream;
|
||||||
this.paddingLength = builder.paddingLength;
|
this.paddingLength = builder.paddingLength;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getStreamId() {
|
|
||||||
return streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEndOfStream() {
|
|
||||||
return endOfStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPaddingLength() {
|
|
||||||
return paddingLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DefaultHttp2DataFrame copy() {
|
|
||||||
return copyBuilder().setContent(content().copy()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DefaultHttp2DataFrame duplicate() {
|
|
||||||
return copyBuilder().setContent(content().duplicate()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DefaultHttp2DataFrame retain() {
|
|
||||||
super.retain();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DefaultHttp2DataFrame retain(int increment) {
|
|
||||||
super.retain(increment);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DefaultHttp2DataFrame touch() {
|
|
||||||
super.touch();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DefaultHttp2DataFrame touch(Object hint) {
|
|
||||||
super.touch(hint);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = content().hashCode();
|
|
||||||
result = prime * result + (endOfStream ? 1231 : 1237);
|
|
||||||
result = prime * result + paddingLength;
|
|
||||||
result = prime * result + streamId;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DefaultHttp2DataFrame other = (DefaultHttp2DataFrame) obj;
|
|
||||||
if (endOfStream != other.endOfStream) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (paddingLength != other.paddingLength) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (streamId != other.streamId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!content().equals(other.content())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder copyBuilder() {
|
|
||||||
return new Builder().setStreamId(streamId).setPaddingLength(paddingLength)
|
|
||||||
.setEndOfStream(endOfStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds instances of {@link DefaultHttp2DataFrame}.
|
|
||||||
*/
|
|
||||||
public static class Builder {
|
|
||||||
private int streamId;
|
|
||||||
private boolean endOfStream;
|
|
||||||
private ByteBuf content = Unpooled.EMPTY_BUFFER;
|
|
||||||
private int paddingLength;
|
|
||||||
|
|
||||||
public Builder setStreamId(int streamId) {
|
|
||||||
if (streamId <= 0) {
|
|
||||||
throw new IllegalArgumentException("StreamId must be > 0.");
|
|
||||||
}
|
|
||||||
this.streamId = streamId;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setEndOfStream(boolean endOfStream) {
|
@Override
|
||||||
this.endOfStream = endOfStream;
|
public int getStreamId() {
|
||||||
return this;
|
return streamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEndOfStream() {
|
||||||
|
return endOfStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPaddingLength() {
|
||||||
|
return paddingLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultHttp2DataFrame copy() {
|
||||||
|
return copyBuilder().setContent(content().copy()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultHttp2DataFrame duplicate() {
|
||||||
|
return copyBuilder().setContent(content().duplicate()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultHttp2DataFrame retain() {
|
||||||
|
super.retain();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultHttp2DataFrame retain(int increment) {
|
||||||
|
super.retain(increment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultHttp2DataFrame touch() {
|
||||||
|
super.touch();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultHttp2DataFrame touch(Object hint) {
|
||||||
|
super.touch(hint);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = content().hashCode();
|
||||||
|
result = prime * result + (endOfStream ? 1231 : 1237);
|
||||||
|
result = prime * result + paddingLength;
|
||||||
|
result = prime * result + streamId;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DefaultHttp2DataFrame other = (DefaultHttp2DataFrame) obj;
|
||||||
|
if (endOfStream != other.endOfStream) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (paddingLength != other.paddingLength) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (streamId != other.streamId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!content().equals(other.content())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Builder copyBuilder() {
|
||||||
|
return new Builder().setStreamId(streamId).setPaddingLength(paddingLength)
|
||||||
|
.setEndOfStream(endOfStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the content for the data frame, excluding any padding. This buffer will be retained when
|
* Builds instances of {@link DefaultHttp2DataFrame}.
|
||||||
* the frame is built.
|
|
||||||
*/
|
*/
|
||||||
public Builder setContent(ByteBuf content) {
|
public static class Builder {
|
||||||
if (content == null) {
|
private int streamId;
|
||||||
throw new IllegalArgumentException("content must not be null");
|
private boolean endOfStream;
|
||||||
}
|
private ByteBuf content = Unpooled.EMPTY_BUFFER;
|
||||||
verifyLength(paddingLength, content);
|
private int paddingLength;
|
||||||
this.content = content;
|
|
||||||
return this;
|
public Builder setStreamId(int streamId) {
|
||||||
|
if (streamId <= 0) {
|
||||||
|
throw new IllegalArgumentException("StreamId must be > 0.");
|
||||||
|
}
|
||||||
|
this.streamId = streamId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setEndOfStream(boolean endOfStream) {
|
||||||
|
this.endOfStream = endOfStream;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the content for the data frame, excluding any padding. This buffer will be retained when
|
||||||
|
* the frame is built.
|
||||||
|
*/
|
||||||
|
public Builder setContent(ByteBuf content) {
|
||||||
|
if (content == null) {
|
||||||
|
throw new IllegalArgumentException("content must not be null");
|
||||||
|
}
|
||||||
|
verifyLength(paddingLength, content);
|
||||||
|
this.content = content;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setPaddingLength(int paddingLength) {
|
||||||
|
if (paddingLength < 0 || paddingLength > MAX_UNSIGNED_SHORT) {
|
||||||
|
throw new IllegalArgumentException("Padding length invalid.");
|
||||||
|
}
|
||||||
|
verifyLength(paddingLength, content);
|
||||||
|
this.paddingLength = paddingLength;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultHttp2DataFrame build() {
|
||||||
|
if (streamId <= 0) {
|
||||||
|
throw new IllegalArgumentException("StreamId must be set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyLength(paddingLength, content);
|
||||||
|
|
||||||
|
return new DefaultHttp2DataFrame(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyLength(int paddingLength, ByteBuf data) {
|
||||||
|
int maxLength = MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
|
maxLength -= paddingLength;
|
||||||
|
if (data.readableBytes() > maxLength) {
|
||||||
|
throw new IllegalArgumentException("Header block fragment length too big.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setPaddingLength(int paddingLength) {
|
|
||||||
if (paddingLength < 0 || paddingLength > MAX_UNSIGNED_SHORT) {
|
|
||||||
throw new IllegalArgumentException("Padding length invalid.");
|
|
||||||
}
|
|
||||||
verifyLength(paddingLength, content);
|
|
||||||
this.paddingLength = paddingLength;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultHttp2DataFrame build() {
|
|
||||||
if (streamId <= 0) {
|
|
||||||
throw new IllegalArgumentException("StreamId must be set.");
|
|
||||||
}
|
|
||||||
|
|
||||||
verifyLength(paddingLength, content);
|
|
||||||
|
|
||||||
return new DefaultHttp2DataFrame(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyLength(int paddingLength, ByteBuf data) {
|
|
||||||
int maxLength = MAX_FRAME_PAYLOAD_LENGTH;
|
|
||||||
maxLength -= paddingLength;
|
|
||||||
if (data.readableBytes() > maxLength) {
|
|
||||||
throw new IllegalArgumentException("Header block fragment length too big.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package io.netty.handler.codec.http2.draft10.frame;
|
|||||||
|
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_UNSIGNED_INT;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_UNSIGNED_INT;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.DefaultByteBufHolder;
|
import io.netty.buffer.DefaultByteBufHolder;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
@ -25,139 +26,139 @@ import io.netty.buffer.Unpooled;
|
|||||||
* Default implementation of {@link Http2GoAwayFrame}.
|
* Default implementation of {@link Http2GoAwayFrame}.
|
||||||
*/
|
*/
|
||||||
public final class DefaultHttp2GoAwayFrame extends DefaultByteBufHolder implements
|
public final class DefaultHttp2GoAwayFrame extends DefaultByteBufHolder implements
|
||||||
Http2GoAwayFrame {
|
Http2GoAwayFrame {
|
||||||
private final int lastStreamId;
|
private final int lastStreamId;
|
||||||
private final long errorCode;
|
private final long errorCode;
|
||||||
|
|
||||||
private DefaultHttp2GoAwayFrame(Builder builder) {
|
private DefaultHttp2GoAwayFrame(Builder builder) {
|
||||||
super(builder.debugData);
|
super(builder.debugData);
|
||||||
this.lastStreamId = builder.lastStreamId;
|
this.lastStreamId = builder.lastStreamId;
|
||||||
this.errorCode = builder.errorCode;
|
this.errorCode = builder.errorCode;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLastStreamId() {
|
|
||||||
return lastStreamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getErrorCode() {
|
|
||||||
return errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DefaultHttp2GoAwayFrame copy() {
|
|
||||||
return copyBuilder().setDebugData(content().copy()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DefaultHttp2GoAwayFrame duplicate() {
|
|
||||||
return copyBuilder().setDebugData(content().duplicate()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DefaultHttp2GoAwayFrame retain() {
|
|
||||||
super.retain();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DefaultHttp2GoAwayFrame retain(int increment) {
|
|
||||||
super.retain(increment);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DefaultHttp2GoAwayFrame touch() {
|
|
||||||
super.touch();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DefaultHttp2GoAwayFrame touch(Object hint) {
|
|
||||||
super.touch(hint);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = content().hashCode();
|
|
||||||
result = prime * result + (int) (errorCode ^ (errorCode >>> 32));
|
|
||||||
result = prime * result + lastStreamId;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DefaultHttp2GoAwayFrame other = (DefaultHttp2GoAwayFrame) obj;
|
|
||||||
if (errorCode != other.errorCode) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (lastStreamId != other.lastStreamId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!content().equals(other.content())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder copyBuilder() {
|
|
||||||
return new Builder().setErrorCode(errorCode).setLastStreamId(lastStreamId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds instances of {@link DefaultHttp2GoAwayFrame}.
|
|
||||||
*/
|
|
||||||
public static class Builder {
|
|
||||||
private int lastStreamId = -1;
|
|
||||||
private long errorCode = -1;
|
|
||||||
private ByteBuf debugData = Unpooled.EMPTY_BUFFER;
|
|
||||||
|
|
||||||
public Builder setLastStreamId(int lastStreamId) {
|
|
||||||
if (lastStreamId < 0) {
|
|
||||||
throw new IllegalArgumentException("Invalid lastStreamId.");
|
|
||||||
}
|
|
||||||
this.lastStreamId = lastStreamId;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setErrorCode(long errorCode) {
|
@Override
|
||||||
if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) {
|
public int getLastStreamId() {
|
||||||
throw new IllegalArgumentException("Invalid error code.");
|
return lastStreamId;
|
||||||
}
|
|
||||||
this.errorCode = errorCode;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setDebugData(ByteBuf debugData) {
|
@Override
|
||||||
if (debugData == null) {
|
public long getErrorCode() {
|
||||||
throw new IllegalArgumentException("debugData must not be null");
|
return errorCode;
|
||||||
}
|
|
||||||
if (debugData.readableBytes() > MAX_FRAME_PAYLOAD_LENGTH - 8) {
|
|
||||||
throw new IllegalArgumentException("Invalid debug data size.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.debugData = debugData;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultHttp2GoAwayFrame build() {
|
@Override
|
||||||
if (lastStreamId < 0) {
|
public DefaultHttp2GoAwayFrame copy() {
|
||||||
throw new IllegalArgumentException("LastStreamId must be set");
|
return copyBuilder().setDebugData(content().copy()).build();
|
||||||
}
|
}
|
||||||
if (errorCode < 0) {
|
|
||||||
throw new IllegalArgumentException("ErrorCode must be set.");
|
@Override
|
||||||
}
|
public DefaultHttp2GoAwayFrame duplicate() {
|
||||||
|
return copyBuilder().setDebugData(content().duplicate()).build();
|
||||||
return new DefaultHttp2GoAwayFrame(this);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultHttp2GoAwayFrame retain() {
|
||||||
|
super.retain();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultHttp2GoAwayFrame retain(int increment) {
|
||||||
|
super.retain(increment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultHttp2GoAwayFrame touch() {
|
||||||
|
super.touch();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultHttp2GoAwayFrame touch(Object hint) {
|
||||||
|
super.touch(hint);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = content().hashCode();
|
||||||
|
result = prime * result + (int) (errorCode ^ (errorCode >>> 32));
|
||||||
|
result = prime * result + lastStreamId;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DefaultHttp2GoAwayFrame other = (DefaultHttp2GoAwayFrame) obj;
|
||||||
|
if (errorCode != other.errorCode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (lastStreamId != other.lastStreamId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!content().equals(other.content())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Builder copyBuilder() {
|
||||||
|
return new Builder().setErrorCode(errorCode).setLastStreamId(lastStreamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds instances of {@link DefaultHttp2GoAwayFrame}.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private int lastStreamId = -1;
|
||||||
|
private long errorCode = -1;
|
||||||
|
private ByteBuf debugData = Unpooled.EMPTY_BUFFER;
|
||||||
|
|
||||||
|
public Builder setLastStreamId(int lastStreamId) {
|
||||||
|
if (lastStreamId < 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid lastStreamId.");
|
||||||
|
}
|
||||||
|
this.lastStreamId = lastStreamId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setErrorCode(long errorCode) {
|
||||||
|
if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) {
|
||||||
|
throw new IllegalArgumentException("Invalid error code.");
|
||||||
|
}
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setDebugData(ByteBuf debugData) {
|
||||||
|
if (debugData == null) {
|
||||||
|
throw new IllegalArgumentException("debugData must not be null");
|
||||||
|
}
|
||||||
|
if (debugData.readableBytes() > MAX_FRAME_PAYLOAD_LENGTH - 8) {
|
||||||
|
throw new IllegalArgumentException("Invalid debug data size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.debugData = debugData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultHttp2GoAwayFrame build() {
|
||||||
|
if (lastStreamId < 0) {
|
||||||
|
throw new IllegalArgumentException("LastStreamId must be set");
|
||||||
|
}
|
||||||
|
if (errorCode < 0) {
|
||||||
|
throw new IllegalArgumentException("ErrorCode must be set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DefaultHttp2GoAwayFrame(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package io.netty.handler.codec.http2.draft10.frame;
|
package io.netty.handler.codec.http2.draft10.frame;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.DEFAULT_STREAM_PRIORITY;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.DEFAULT_STREAM_PRIORITY;
|
||||||
|
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Headers;
|
import io.netty.handler.codec.http2.draft10.Http2Headers;
|
||||||
|
|
||||||
public final class DefaultHttp2HeadersFrame implements Http2HeadersFrame {
|
public final class DefaultHttp2HeadersFrame implements Http2HeadersFrame {
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package io.netty.handler.codec.http2.draft10.frame;
|
package io.netty.handler.codec.http2.draft10.frame;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.PING_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.PING_FRAME_PAYLOAD_LENGTH;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.DefaultByteBufHolder;
|
import io.netty.buffer.DefaultByteBufHolder;
|
||||||
|
|
||||||
|
@ -20,90 +20,90 @@ package io.netty.handler.codec.http2.draft10.frame;
|
|||||||
*/
|
*/
|
||||||
public final class DefaultHttp2PriorityFrame implements Http2PriorityFrame {
|
public final class DefaultHttp2PriorityFrame implements Http2PriorityFrame {
|
||||||
|
|
||||||
private final int streamId;
|
private final int streamId;
|
||||||
private final int priority;
|
private final int priority;
|
||||||
|
|
||||||
private DefaultHttp2PriorityFrame(Builder builder) {
|
private DefaultHttp2PriorityFrame(Builder builder) {
|
||||||
this.streamId = builder.streamId;
|
this.streamId = builder.streamId;
|
||||||
this.priority = builder.priority;
|
this.priority = builder.priority;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getStreamId() {
|
|
||||||
return streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEndOfStream() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + priority;
|
|
||||||
result = prime * result + streamId;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DefaultHttp2PriorityFrame other = (DefaultHttp2PriorityFrame) obj;
|
|
||||||
if (priority != other.priority) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (streamId != other.streamId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds instances of {@link DefaultHttp2PriorityFrame}.
|
|
||||||
*/
|
|
||||||
public static class Builder {
|
|
||||||
private int streamId;
|
|
||||||
private int priority = -1;
|
|
||||||
|
|
||||||
public Builder setStreamId(int streamId) {
|
|
||||||
if (streamId <= 0) {
|
|
||||||
throw new IllegalArgumentException("StreamId must be > 0.");
|
|
||||||
}
|
|
||||||
this.streamId = streamId;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setPriority(int priority) {
|
@Override
|
||||||
if (priority < 0) {
|
public int getStreamId() {
|
||||||
throw new IllegalArgumentException("Invalid priority.");
|
return streamId;
|
||||||
}
|
|
||||||
this.priority = priority;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultHttp2PriorityFrame build() {
|
@Override
|
||||||
if (streamId <= 0) {
|
public int getPriority() {
|
||||||
throw new IllegalArgumentException("StreamId must be set.");
|
return priority;
|
||||||
}
|
}
|
||||||
if (priority < 0) {
|
|
||||||
throw new IllegalArgumentException("Priority must be set.");
|
@Override
|
||||||
}
|
public boolean isEndOfStream() {
|
||||||
return new DefaultHttp2PriorityFrame(this);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + priority;
|
||||||
|
result = prime * result + streamId;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DefaultHttp2PriorityFrame other = (DefaultHttp2PriorityFrame) obj;
|
||||||
|
if (priority != other.priority) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (streamId != other.streamId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds instances of {@link DefaultHttp2PriorityFrame}.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private int streamId;
|
||||||
|
private int priority = -1;
|
||||||
|
|
||||||
|
public Builder setStreamId(int streamId) {
|
||||||
|
if (streamId <= 0) {
|
||||||
|
throw new IllegalArgumentException("StreamId must be > 0.");
|
||||||
|
}
|
||||||
|
this.streamId = streamId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setPriority(int priority) {
|
||||||
|
if (priority < 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid priority.");
|
||||||
|
}
|
||||||
|
this.priority = priority;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultHttp2PriorityFrame build() {
|
||||||
|
if (streamId <= 0) {
|
||||||
|
throw new IllegalArgumentException("StreamId must be set.");
|
||||||
|
}
|
||||||
|
if (priority < 0) {
|
||||||
|
throw new IllegalArgumentException("Priority must be set.");
|
||||||
|
}
|
||||||
|
return new DefaultHttp2PriorityFrame(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,120 +19,120 @@ import io.netty.handler.codec.http2.draft10.Http2Headers;
|
|||||||
|
|
||||||
public final class DefaultHttp2PushPromiseFrame implements Http2PushPromiseFrame {
|
public final class DefaultHttp2PushPromiseFrame implements Http2PushPromiseFrame {
|
||||||
|
|
||||||
private final int streamId;
|
private final int streamId;
|
||||||
private final int promisedStreamId;
|
private final int promisedStreamId;
|
||||||
private final Http2Headers headers;
|
private final Http2Headers headers;
|
||||||
|
|
||||||
private DefaultHttp2PushPromiseFrame(Builder builder) {
|
private DefaultHttp2PushPromiseFrame(Builder builder) {
|
||||||
this.streamId = builder.streamId;
|
this.streamId = builder.streamId;
|
||||||
this.promisedStreamId = builder.promisedStreamId;
|
this.promisedStreamId = builder.promisedStreamId;
|
||||||
this.headers = builder.headers;
|
this.headers = builder.headers;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getStreamId() {
|
|
||||||
return streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEndOfStream() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPromisedStreamId() {
|
|
||||||
return promisedStreamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Http2Headers getHeaders() {
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((headers == null) ? 0 : headers.hashCode());
|
|
||||||
result = prime * result + promisedStreamId;
|
|
||||||
result = prime * result + streamId;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
@Override
|
||||||
|
public int getStreamId() {
|
||||||
|
return streamId;
|
||||||
}
|
}
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
@Override
|
||||||
}
|
public boolean isEndOfStream() {
|
||||||
DefaultHttp2PushPromiseFrame other = (DefaultHttp2PushPromiseFrame) obj;
|
|
||||||
if (headers == null) {
|
|
||||||
if (other.headers != null) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
} else if (!headers.equals(other.headers)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (promisedStreamId != other.promisedStreamId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (streamId != other.streamId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "DefaultHttp2PushPromiseFrame [streamId=" + streamId + ", promisedStreamId="
|
|
||||||
+ promisedStreamId + ", headers=" + headers + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
private int streamId;
|
|
||||||
private int promisedStreamId;
|
|
||||||
private Http2Headers headers;
|
|
||||||
|
|
||||||
public Builder setStreamId(int streamId) {
|
|
||||||
if (streamId <= 0) {
|
|
||||||
throw new IllegalArgumentException("StreamId must be > 0.");
|
|
||||||
}
|
|
||||||
this.streamId = streamId;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setPromisedStreamId(int promisedStreamId) {
|
@Override
|
||||||
if (promisedStreamId <= 0) {
|
public int getPromisedStreamId() {
|
||||||
throw new IllegalArgumentException("promisedStreamId must be > 0.");
|
return promisedStreamId;
|
||||||
}
|
|
||||||
this.promisedStreamId = promisedStreamId;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setHeaders(Http2Headers headers) {
|
@Override
|
||||||
if (headers == null) {
|
public Http2Headers getHeaders() {
|
||||||
throw new IllegalArgumentException("headers must not be null.");
|
return headers;
|
||||||
}
|
|
||||||
this.headers = headers;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultHttp2PushPromiseFrame build() {
|
@Override
|
||||||
if (streamId <= 0) {
|
public int hashCode() {
|
||||||
throw new IllegalArgumentException("StreamId must be set.");
|
final int prime = 31;
|
||||||
}
|
int result = 1;
|
||||||
if (promisedStreamId <= 0) {
|
result = prime * result + ((headers == null) ? 0 : headers.hashCode());
|
||||||
throw new IllegalArgumentException("promisedStreamId must be set.");
|
result = prime * result + promisedStreamId;
|
||||||
}
|
result = prime * result + streamId;
|
||||||
if (headers == null) {
|
return result;
|
||||||
throw new IllegalArgumentException("headers must be set.");
|
}
|
||||||
}
|
|
||||||
return new DefaultHttp2PushPromiseFrame(this);
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DefaultHttp2PushPromiseFrame other = (DefaultHttp2PushPromiseFrame) obj;
|
||||||
|
if (headers == null) {
|
||||||
|
if (other.headers != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!headers.equals(other.headers)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (promisedStreamId != other.promisedStreamId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (streamId != other.streamId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DefaultHttp2PushPromiseFrame [streamId=" + streamId + ", promisedStreamId="
|
||||||
|
+ promisedStreamId + ", headers=" + headers + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private int streamId;
|
||||||
|
private int promisedStreamId;
|
||||||
|
private Http2Headers headers;
|
||||||
|
|
||||||
|
public Builder setStreamId(int streamId) {
|
||||||
|
if (streamId <= 0) {
|
||||||
|
throw new IllegalArgumentException("StreamId must be > 0.");
|
||||||
|
}
|
||||||
|
this.streamId = streamId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setPromisedStreamId(int promisedStreamId) {
|
||||||
|
if (promisedStreamId <= 0) {
|
||||||
|
throw new IllegalArgumentException("promisedStreamId must be > 0.");
|
||||||
|
}
|
||||||
|
this.promisedStreamId = promisedStreamId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setHeaders(Http2Headers headers) {
|
||||||
|
if (headers == null) {
|
||||||
|
throw new IllegalArgumentException("headers must not be null.");
|
||||||
|
}
|
||||||
|
this.headers = headers;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultHttp2PushPromiseFrame build() {
|
||||||
|
if (streamId <= 0) {
|
||||||
|
throw new IllegalArgumentException("StreamId must be set.");
|
||||||
|
}
|
||||||
|
if (promisedStreamId <= 0) {
|
||||||
|
throw new IllegalArgumentException("promisedStreamId must be set.");
|
||||||
|
}
|
||||||
|
if (headers == null) {
|
||||||
|
throw new IllegalArgumentException("headers must be set.");
|
||||||
|
}
|
||||||
|
return new DefaultHttp2PushPromiseFrame(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -21,90 +21,90 @@ import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX
|
|||||||
* Default implementation of {@link Http2RstStreamFrame}.
|
* Default implementation of {@link Http2RstStreamFrame}.
|
||||||
*/
|
*/
|
||||||
public final class DefaultHttp2RstStreamFrame implements Http2RstStreamFrame {
|
public final class DefaultHttp2RstStreamFrame implements Http2RstStreamFrame {
|
||||||
private final int streamId;
|
private final int streamId;
|
||||||
private final long errorCode;
|
private final long errorCode;
|
||||||
|
|
||||||
private DefaultHttp2RstStreamFrame(Builder builder) {
|
private DefaultHttp2RstStreamFrame(Builder builder) {
|
||||||
this.streamId = builder.streamId;
|
this.streamId = builder.streamId;
|
||||||
this.errorCode = builder.errorCode;
|
this.errorCode = builder.errorCode;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getStreamId() {
|
|
||||||
return streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getErrorCode() {
|
|
||||||
return errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEndOfStream() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + (int) (errorCode ^ (errorCode >>> 32));
|
|
||||||
result = prime * result + streamId;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DefaultHttp2RstStreamFrame other = (DefaultHttp2RstStreamFrame) obj;
|
|
||||||
if (errorCode != other.errorCode) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (streamId != other.streamId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds instances of {@link DefaultHttp2RstStreamFrame}.
|
|
||||||
*/
|
|
||||||
public static class Builder {
|
|
||||||
private int streamId;
|
|
||||||
private long errorCode = -1L;
|
|
||||||
|
|
||||||
public Builder setStreamId(int streamId) {
|
|
||||||
if (streamId <= 0) {
|
|
||||||
throw new IllegalArgumentException("StreamId must be > 0.");
|
|
||||||
}
|
|
||||||
this.streamId = streamId;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setErrorCode(long errorCode) {
|
@Override
|
||||||
if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) {
|
public int getStreamId() {
|
||||||
throw new IllegalArgumentException("Invalid errorCode value.");
|
return streamId;
|
||||||
}
|
|
||||||
this.errorCode = errorCode;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultHttp2RstStreamFrame build() {
|
@Override
|
||||||
if (streamId <= 0) {
|
public long getErrorCode() {
|
||||||
throw new IllegalArgumentException("StreamId must be set.");
|
return errorCode;
|
||||||
}
|
}
|
||||||
if (errorCode < 0L) {
|
|
||||||
throw new IllegalArgumentException("ErrorCode must be set.");
|
@Override
|
||||||
}
|
public boolean isEndOfStream() {
|
||||||
return new DefaultHttp2RstStreamFrame(this);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + (int) (errorCode ^ (errorCode >>> 32));
|
||||||
|
result = prime * result + streamId;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DefaultHttp2RstStreamFrame other = (DefaultHttp2RstStreamFrame) obj;
|
||||||
|
if (errorCode != other.errorCode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (streamId != other.streamId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds instances of {@link DefaultHttp2RstStreamFrame}.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private int streamId;
|
||||||
|
private long errorCode = -1L;
|
||||||
|
|
||||||
|
public Builder setStreamId(int streamId) {
|
||||||
|
if (streamId <= 0) {
|
||||||
|
throw new IllegalArgumentException("StreamId must be > 0.");
|
||||||
|
}
|
||||||
|
this.streamId = streamId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setErrorCode(long errorCode) {
|
||||||
|
if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) {
|
||||||
|
throw new IllegalArgumentException("Invalid errorCode value.");
|
||||||
|
}
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultHttp2RstStreamFrame build() {
|
||||||
|
if (streamId <= 0) {
|
||||||
|
throw new IllegalArgumentException("StreamId must be set.");
|
||||||
|
}
|
||||||
|
if (errorCode < 0L) {
|
||||||
|
throw new IllegalArgumentException("ErrorCode must be set.");
|
||||||
|
}
|
||||||
|
return new DefaultHttp2RstStreamFrame(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -20,145 +20,145 @@ package io.netty.handler.codec.http2.draft10.frame;
|
|||||||
*/
|
*/
|
||||||
public final class DefaultHttp2SettingsFrame implements Http2SettingsFrame {
|
public final class DefaultHttp2SettingsFrame implements Http2SettingsFrame {
|
||||||
|
|
||||||
private final boolean ack;
|
private final boolean ack;
|
||||||
private final Integer headerTableSize;
|
private final Integer headerTableSize;
|
||||||
private final Boolean pushEnabled;
|
private final Boolean pushEnabled;
|
||||||
private final Long maxConcurrentStreams;
|
private final Long maxConcurrentStreams;
|
||||||
private final Integer initialWindowSize;
|
private final Integer initialWindowSize;
|
||||||
|
|
||||||
private DefaultHttp2SettingsFrame(Builder builder) {
|
private DefaultHttp2SettingsFrame(Builder builder) {
|
||||||
this.ack = builder.ack;
|
this.ack = builder.ack;
|
||||||
this.headerTableSize = builder.headerTableSize;
|
this.headerTableSize = builder.headerTableSize;
|
||||||
this.pushEnabled = builder.pushEnabled;
|
this.pushEnabled = builder.pushEnabled;
|
||||||
this.maxConcurrentStreams = builder.maxConcurrentStreams;
|
this.maxConcurrentStreams = builder.maxConcurrentStreams;
|
||||||
this.initialWindowSize = builder.initialWindowSize;
|
this.initialWindowSize = builder.initialWindowSize;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAck() {
|
|
||||||
return ack;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Integer getHeaderTableSize() {
|
|
||||||
return headerTableSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Boolean getPushEnabled() {
|
|
||||||
return pushEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long getMaxConcurrentStreams() {
|
|
||||||
return maxConcurrentStreams;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Integer getInitialWindowSize() {
|
|
||||||
return initialWindowSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + (ack ? 1231 : 1237);
|
|
||||||
result = prime * result + ((headerTableSize == null) ? 0 : headerTableSize.hashCode());
|
|
||||||
result = prime * result + ((initialWindowSize == null) ? 0 : initialWindowSize.hashCode());
|
|
||||||
result =
|
|
||||||
prime * result + ((maxConcurrentStreams == null) ? 0 : maxConcurrentStreams.hashCode());
|
|
||||||
result = prime * result + ((pushEnabled == null) ? 0 : pushEnabled.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DefaultHttp2SettingsFrame other = (DefaultHttp2SettingsFrame) obj;
|
|
||||||
if (ack != other.ack) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (headerTableSize == null) {
|
|
||||||
if (other.headerTableSize != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!headerTableSize.equals(other.headerTableSize)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (initialWindowSize == null) {
|
|
||||||
if (other.initialWindowSize != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!initialWindowSize.equals(other.initialWindowSize)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (maxConcurrentStreams == null) {
|
|
||||||
if (other.maxConcurrentStreams != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!maxConcurrentStreams.equals(other.maxConcurrentStreams)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (pushEnabled == null) {
|
|
||||||
if (other.pushEnabled != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!pushEnabled.equals(other.pushEnabled)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds instances of {@link DefaultHttp2SettingsFrame}.
|
|
||||||
*/
|
|
||||||
public static class Builder {
|
|
||||||
private boolean ack;
|
|
||||||
private Integer headerTableSize;
|
|
||||||
private Boolean pushEnabled;
|
|
||||||
private Long maxConcurrentStreams;
|
|
||||||
private Integer initialWindowSize;
|
|
||||||
|
|
||||||
public Builder setAck(boolean ack) {
|
|
||||||
this.ack = ack;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setHeaderTableSize(int headerTableSize) {
|
@Override
|
||||||
this.headerTableSize = headerTableSize;
|
public boolean isAck() {
|
||||||
return this;
|
return ack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setPushEnabled(boolean pushEnabled) {
|
@Override
|
||||||
this.pushEnabled = pushEnabled;
|
public Integer getHeaderTableSize() {
|
||||||
return this;
|
return headerTableSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setMaxConcurrentStreams(long maxConcurrentStreams) {
|
@Override
|
||||||
this.maxConcurrentStreams = maxConcurrentStreams;
|
public Boolean getPushEnabled() {
|
||||||
return this;
|
return pushEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setInitialWindowSize(int initialWindowSize) {
|
@Override
|
||||||
this.initialWindowSize = initialWindowSize;
|
public Long getMaxConcurrentStreams() {
|
||||||
return this;
|
return maxConcurrentStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultHttp2SettingsFrame build() {
|
@Override
|
||||||
if (ack && (headerTableSize != null || pushEnabled != null || maxConcurrentStreams != null
|
public Integer getInitialWindowSize() {
|
||||||
|| initialWindowSize != null)) {
|
return initialWindowSize;
|
||||||
throw new IllegalArgumentException("Ack frame must not contain settings");
|
}
|
||||||
}
|
|
||||||
return new DefaultHttp2SettingsFrame(this);
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + (ack ? 1231 : 1237);
|
||||||
|
result = prime * result + ((headerTableSize == null) ? 0 : headerTableSize.hashCode());
|
||||||
|
result = prime * result + ((initialWindowSize == null) ? 0 : initialWindowSize.hashCode());
|
||||||
|
result =
|
||||||
|
prime * result + ((maxConcurrentStreams == null) ? 0 : maxConcurrentStreams.hashCode());
|
||||||
|
result = prime * result + ((pushEnabled == null) ? 0 : pushEnabled.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DefaultHttp2SettingsFrame other = (DefaultHttp2SettingsFrame) obj;
|
||||||
|
if (ack != other.ack) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (headerTableSize == null) {
|
||||||
|
if (other.headerTableSize != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!headerTableSize.equals(other.headerTableSize)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (initialWindowSize == null) {
|
||||||
|
if (other.initialWindowSize != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!initialWindowSize.equals(other.initialWindowSize)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (maxConcurrentStreams == null) {
|
||||||
|
if (other.maxConcurrentStreams != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!maxConcurrentStreams.equals(other.maxConcurrentStreams)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pushEnabled == null) {
|
||||||
|
if (other.pushEnabled != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!pushEnabled.equals(other.pushEnabled)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds instances of {@link DefaultHttp2SettingsFrame}.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private boolean ack;
|
||||||
|
private Integer headerTableSize;
|
||||||
|
private Boolean pushEnabled;
|
||||||
|
private Long maxConcurrentStreams;
|
||||||
|
private Integer initialWindowSize;
|
||||||
|
|
||||||
|
public Builder setAck(boolean ack) {
|
||||||
|
this.ack = ack;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setHeaderTableSize(int headerTableSize) {
|
||||||
|
this.headerTableSize = headerTableSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setPushEnabled(boolean pushEnabled) {
|
||||||
|
this.pushEnabled = pushEnabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setMaxConcurrentStreams(long maxConcurrentStreams) {
|
||||||
|
this.maxConcurrentStreams = maxConcurrentStreams;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setInitialWindowSize(int initialWindowSize) {
|
||||||
|
this.initialWindowSize = initialWindowSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultHttp2SettingsFrame build() {
|
||||||
|
if (ack && (headerTableSize != null || pushEnabled != null || maxConcurrentStreams != null
|
||||||
|
|| initialWindowSize != null)) {
|
||||||
|
throw new IllegalArgumentException("Ack frame must not contain settings");
|
||||||
|
}
|
||||||
|
return new DefaultHttp2SettingsFrame(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -20,84 +20,84 @@ package io.netty.handler.codec.http2.draft10.frame;
|
|||||||
*/
|
*/
|
||||||
public final class DefaultHttp2WindowUpdateFrame implements Http2WindowUpdateFrame {
|
public final class DefaultHttp2WindowUpdateFrame implements Http2WindowUpdateFrame {
|
||||||
|
|
||||||
private final int streamId;
|
private final int streamId;
|
||||||
private final int windowSizeIncrement;
|
private final int windowSizeIncrement;
|
||||||
|
|
||||||
private DefaultHttp2WindowUpdateFrame(Builder builder) {
|
private DefaultHttp2WindowUpdateFrame(Builder builder) {
|
||||||
this.streamId = builder.streamId;
|
this.streamId = builder.streamId;
|
||||||
this.windowSizeIncrement = builder.windowSizeIncrement;
|
this.windowSizeIncrement = builder.windowSizeIncrement;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getStreamId() {
|
|
||||||
return streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEndOfStream() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWindowSizeIncrement() {
|
|
||||||
return windowSizeIncrement;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + streamId;
|
|
||||||
result = prime * result + windowSizeIncrement;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DefaultHttp2WindowUpdateFrame other = (DefaultHttp2WindowUpdateFrame) obj;
|
|
||||||
if (streamId != other.streamId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (windowSizeIncrement != other.windowSizeIncrement) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds instances of {@link DefaultHttp2WindowUpdateFrame}.
|
|
||||||
*/
|
|
||||||
public static class Builder {
|
|
||||||
private int streamId;
|
|
||||||
private int windowSizeIncrement;
|
|
||||||
|
|
||||||
public Builder setStreamId(int streamId) {
|
|
||||||
this.streamId = streamId;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setWindowSizeIncrement(int windowSizeIncrement) {
|
@Override
|
||||||
this.windowSizeIncrement = windowSizeIncrement;
|
public int getStreamId() {
|
||||||
return this;
|
return streamId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultHttp2WindowUpdateFrame build() {
|
@Override
|
||||||
if (streamId < 0) {
|
public boolean isEndOfStream() {
|
||||||
throw new IllegalArgumentException("StreamId must be >= 0.");
|
return false;
|
||||||
}
|
}
|
||||||
if (windowSizeIncrement < 0) {
|
|
||||||
throw new IllegalArgumentException("SindowSizeIncrement must be >= 0.");
|
@Override
|
||||||
}
|
public int getWindowSizeIncrement() {
|
||||||
return new DefaultHttp2WindowUpdateFrame(this);
|
return windowSizeIncrement;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + streamId;
|
||||||
|
result = prime * result + windowSizeIncrement;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DefaultHttp2WindowUpdateFrame other = (DefaultHttp2WindowUpdateFrame) obj;
|
||||||
|
if (streamId != other.streamId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (windowSizeIncrement != other.windowSizeIncrement) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds instances of {@link DefaultHttp2WindowUpdateFrame}.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private int streamId;
|
||||||
|
private int windowSizeIncrement;
|
||||||
|
|
||||||
|
public Builder setStreamId(int streamId) {
|
||||||
|
this.streamId = streamId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setWindowSizeIncrement(int windowSizeIncrement) {
|
||||||
|
this.windowSizeIncrement = windowSizeIncrement;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultHttp2WindowUpdateFrame build() {
|
||||||
|
if (streamId < 0) {
|
||||||
|
throw new IllegalArgumentException("StreamId must be >= 0.");
|
||||||
|
}
|
||||||
|
if (windowSizeIncrement < 0) {
|
||||||
|
throw new IllegalArgumentException("SindowSizeIncrement must be >= 0.");
|
||||||
|
}
|
||||||
|
return new DefaultHttp2WindowUpdateFrame(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -23,32 +23,32 @@ import io.netty.buffer.ByteBufHolder;
|
|||||||
*/
|
*/
|
||||||
public interface Http2DataFrame extends Http2StreamFrame, ByteBufHolder {
|
public interface Http2DataFrame extends Http2StreamFrame, ByteBufHolder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The amount of padding to follow the header data in the frame.
|
* The amount of padding to follow the header data in the frame.
|
||||||
*/
|
*/
|
||||||
int getPaddingLength();
|
int getPaddingLength();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data payload of this frame.
|
* Returns the data payload of this frame.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
ByteBuf content();
|
ByteBuf content();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2DataFrame copy();
|
Http2DataFrame copy();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2DataFrame duplicate();
|
Http2DataFrame duplicate();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2DataFrame retain();
|
Http2DataFrame retain();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2DataFrame retain(int increment);
|
Http2DataFrame retain(int increment);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2DataFrame touch();
|
Http2DataFrame touch();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2DataFrame touch(Object hint);
|
Http2DataFrame touch(Object hint);
|
||||||
}
|
}
|
||||||
|
@ -27,112 +27,112 @@ import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLA
|
|||||||
* Provides utility methods for accessing specific flags as defined by the HTTP2 spec.
|
* Provides utility methods for accessing specific flags as defined by the HTTP2 spec.
|
||||||
*/
|
*/
|
||||||
public class Http2Flags {
|
public class Http2Flags {
|
||||||
private final short value;
|
private final short value;
|
||||||
|
|
||||||
public Http2Flags(short value) {
|
public Http2Flags(short value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the underlying flags value.
|
|
||||||
*/
|
|
||||||
public short getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the end-of-stream flag is set.
|
|
||||||
*/
|
|
||||||
public boolean isEndOfStream() {
|
|
||||||
return isSet(FLAG_END_STREAM);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the end-of-segment flag is set.
|
|
||||||
*/
|
|
||||||
public boolean isEndOfSegment() {
|
|
||||||
return isSet(FLAG_END_SEGMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the end-of-headers flag is set.
|
|
||||||
*/
|
|
||||||
public boolean isEndOfHeaders() {
|
|
||||||
return isSet(FLAG_END_HEADERS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the flag is set indicating the presence of the priority field in a HEADERS
|
|
||||||
* frame.
|
|
||||||
*/
|
|
||||||
public boolean isPriorityPresent() {
|
|
||||||
return isSet(FLAG_PRIORITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the flag is set indicating that this frame is an ACK.
|
|
||||||
*/
|
|
||||||
public boolean isAck() {
|
|
||||||
return isSet(FLAG_ACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For frames that include padding, indicates if the pad low field is present.
|
|
||||||
*/
|
|
||||||
public boolean isPadLowPresent() {
|
|
||||||
return isSet(FLAG_PAD_LOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For frames that include padding, indicates if the pad high field is present.
|
|
||||||
*/
|
|
||||||
public boolean isPadHighPresent() {
|
|
||||||
return isSet(FLAG_PAD_HIGH);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether the padding flags are set properly. If pad high is set, pad low must also be
|
|
||||||
* set.
|
|
||||||
*/
|
|
||||||
public boolean isPaddingLengthValid() {
|
|
||||||
return isPadHighPresent() ? isPadLowPresent() : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the number of bytes expected in the padding length field of the payload. This is
|
|
||||||
* determined by the {@link #isPadHighPresent()} and {@link #isPadLowPresent()} flags.
|
|
||||||
*/
|
|
||||||
public int getNumPaddingLengthBytes() {
|
|
||||||
return (isPadHighPresent() ? 1 : 0) + (isPadLowPresent() ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + value;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Http2Flags other = (Http2Flags) obj;
|
|
||||||
if (value != other.value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSet(short mask) {
|
/**
|
||||||
return (value & mask) != 0;
|
* Gets the underlying flags value.
|
||||||
}
|
*/
|
||||||
|
public short getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the end-of-stream flag is set.
|
||||||
|
*/
|
||||||
|
public boolean isEndOfStream() {
|
||||||
|
return isSet(FLAG_END_STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the end-of-segment flag is set.
|
||||||
|
*/
|
||||||
|
public boolean isEndOfSegment() {
|
||||||
|
return isSet(FLAG_END_SEGMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the end-of-headers flag is set.
|
||||||
|
*/
|
||||||
|
public boolean isEndOfHeaders() {
|
||||||
|
return isSet(FLAG_END_HEADERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the flag is set indicating the presence of the priority field in a HEADERS
|
||||||
|
* frame.
|
||||||
|
*/
|
||||||
|
public boolean isPriorityPresent() {
|
||||||
|
return isSet(FLAG_PRIORITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the flag is set indicating that this frame is an ACK.
|
||||||
|
*/
|
||||||
|
public boolean isAck() {
|
||||||
|
return isSet(FLAG_ACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For frames that include padding, indicates if the pad low field is present.
|
||||||
|
*/
|
||||||
|
public boolean isPadLowPresent() {
|
||||||
|
return isSet(FLAG_PAD_LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For frames that include padding, indicates if the pad high field is present.
|
||||||
|
*/
|
||||||
|
public boolean isPadHighPresent() {
|
||||||
|
return isSet(FLAG_PAD_HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the padding flags are set properly. If pad high is set, pad low must also be
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
public boolean isPaddingLengthValid() {
|
||||||
|
return isPadHighPresent() ? isPadLowPresent() : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number of bytes expected in the padding length field of the payload. This is
|
||||||
|
* determined by the {@link #isPadHighPresent()} and {@link #isPadLowPresent()} flags.
|
||||||
|
*/
|
||||||
|
public int getNumPaddingLengthBytes() {
|
||||||
|
return (isPadHighPresent() ? 1 : 0) + (isPadLowPresent() ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + value;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Http2Flags other = (Http2Flags) obj;
|
||||||
|
if (value != other.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSet(short mask) {
|
||||||
|
return (value & mask) != 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,12 +26,12 @@ import io.netty.handler.codec.http2.draft10.frame.encoder.Http2FrameMarshaller;
|
|||||||
*/
|
*/
|
||||||
public class Http2FrameCodec extends ChannelHandlerAppender {
|
public class Http2FrameCodec extends ChannelHandlerAppender {
|
||||||
|
|
||||||
public Http2FrameCodec(Http2FrameMarshaller frameMarshaller,
|
public Http2FrameCodec(Http2FrameMarshaller frameMarshaller,
|
||||||
Http2FrameUnmarshaller frameUnmarshaller) {
|
Http2FrameUnmarshaller frameUnmarshaller) {
|
||||||
super(new Http2FrameEncoder(frameMarshaller), new Http2FrameDecoder(frameUnmarshaller));
|
super(new Http2FrameEncoder(frameMarshaller), new Http2FrameDecoder(frameUnmarshaller));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Http2FrameCodec() {
|
public Http2FrameCodec() {
|
||||||
super(new Http2FrameEncoder(), new Http2FrameDecoder());
|
super(new Http2FrameEncoder(), new Http2FrameDecoder());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,105 +21,105 @@ import io.netty.buffer.ByteBuf;
|
|||||||
* Constants and utility method used for encoding/decoding HTTP2 frames.
|
* Constants and utility method used for encoding/decoding HTTP2 frames.
|
||||||
*/
|
*/
|
||||||
public final class Http2FrameCodecUtil {
|
public final class Http2FrameCodecUtil {
|
||||||
public static final int CONNECTION_STREAM_ID = 0;
|
public static final int CONNECTION_STREAM_ID = 0;
|
||||||
|
|
||||||
public static final int DEFAULT_STREAM_PRIORITY = 0x40000000; // 2^30
|
public static final int DEFAULT_STREAM_PRIORITY = 0x40000000; // 2^30
|
||||||
|
|
||||||
public static final int MAX_FRAME_PAYLOAD_LENGTH = 16383;
|
public static final int MAX_FRAME_PAYLOAD_LENGTH = 16383;
|
||||||
public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
|
public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
|
||||||
public static final short MAX_UNSIGNED_BYTE = 0xFF;
|
public static final short MAX_UNSIGNED_BYTE = 0xFF;
|
||||||
public static final int MAX_UNSIGNED_SHORT = 0xFFFF;
|
public static final int MAX_UNSIGNED_SHORT = 0xFFFF;
|
||||||
public static final long MAX_UNSIGNED_INT = 0xFFFFFFFFL;
|
public static final long MAX_UNSIGNED_INT = 0xFFFFFFFFL;
|
||||||
public static final int FRAME_HEADER_LENGTH = 8;
|
public static final int FRAME_HEADER_LENGTH = 8;
|
||||||
public static final int FRAME_LENGTH_MASK = 0x3FFF;
|
public static final int FRAME_LENGTH_MASK = 0x3FFF;
|
||||||
|
|
||||||
public static final short FRAME_TYPE_DATA = 0x0;
|
public static final short FRAME_TYPE_DATA = 0x0;
|
||||||
public static final short FRAME_TYPE_HEADERS = 0x1;
|
public static final short FRAME_TYPE_HEADERS = 0x1;
|
||||||
public static final short FRAME_TYPE_PRIORITY = 0x2;
|
public static final short FRAME_TYPE_PRIORITY = 0x2;
|
||||||
public static final short FRAME_TYPE_RST_STREAM = 0x3;
|
public static final short FRAME_TYPE_RST_STREAM = 0x3;
|
||||||
public static final short FRAME_TYPE_SETTINGS = 0x4;
|
public static final short FRAME_TYPE_SETTINGS = 0x4;
|
||||||
public static final short FRAME_TYPE_PUSH_PROMISE = 0x5;
|
public static final short FRAME_TYPE_PUSH_PROMISE = 0x5;
|
||||||
public static final short FRAME_TYPE_PING = 0x6;
|
public static final short FRAME_TYPE_PING = 0x6;
|
||||||
public static final short FRAME_TYPE_GO_AWAY = 0x7;
|
public static final short FRAME_TYPE_GO_AWAY = 0x7;
|
||||||
public static final short FRAME_TYPE_WINDOW_UPDATE = 0x8;
|
public static final short FRAME_TYPE_WINDOW_UPDATE = 0x8;
|
||||||
public static final short FRAME_TYPE_CONTINUATION = 0x9;
|
public static final short FRAME_TYPE_CONTINUATION = 0x9;
|
||||||
|
|
||||||
public static final short SETTINGS_HEADER_TABLE_SIZE = 1;
|
public static final short SETTINGS_HEADER_TABLE_SIZE = 1;
|
||||||
public static final short SETTINGS_ENABLE_PUSH = 2;
|
public static final short SETTINGS_ENABLE_PUSH = 2;
|
||||||
public static final short SETTINGS_MAX_CONCURRENT_STREAMS = 3;
|
public static final short SETTINGS_MAX_CONCURRENT_STREAMS = 3;
|
||||||
public static final short SETTINGS_INITIAL_WINDOW_SIZE = 4;
|
public static final short SETTINGS_INITIAL_WINDOW_SIZE = 4;
|
||||||
|
|
||||||
public static final short FLAG_END_STREAM = 0x1;
|
public static final short FLAG_END_STREAM = 0x1;
|
||||||
public static final short FLAG_END_SEGMENT = 0x2;
|
public static final short FLAG_END_SEGMENT = 0x2;
|
||||||
public static final short FLAG_END_HEADERS = 0x4;
|
public static final short FLAG_END_HEADERS = 0x4;
|
||||||
public static final short FLAG_PRIORITY = 0x8;
|
public static final short FLAG_PRIORITY = 0x8;
|
||||||
public static final short FLAG_ACK = 0x1;
|
public static final short FLAG_ACK = 0x1;
|
||||||
public static final short FLAG_PAD_LOW = 0x10;
|
public static final short FLAG_PAD_LOW = 0x10;
|
||||||
public static final short FLAG_PAD_HIGH = 0x20;
|
public static final short FLAG_PAD_HIGH = 0x20;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a big-endian (31-bit) integer from the buffer.
|
* Reads a big-endian (31-bit) integer from the buffer.
|
||||||
*/
|
*/
|
||||||
public static int readUnsignedInt(ByteBuf buf) {
|
public static int readUnsignedInt(ByteBuf buf) {
|
||||||
int offset = buf.readerIndex();
|
int offset = buf.readerIndex();
|
||||||
int value = (buf.getByte(offset + 0) & 0x7F) << 24 | (buf.getByte(offset + 1) & 0xFF) << 16
|
int value = (buf.getByte(offset + 0) & 0x7F) << 24 | (buf.getByte(offset + 1) & 0xFF) << 16
|
||||||
| (buf.getByte(offset + 2) & 0xFF) << 8 | buf.getByte(offset + 3) & 0xFF;
|
| (buf.getByte(offset + 2) & 0xFF) << 8 | buf.getByte(offset + 3) & 0xFF;
|
||||||
buf.skipBytes(4);
|
buf.skipBytes(4);
|
||||||
return value;
|
return value;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a big-endian (32-bit) unsigned integer to the buffer.
|
|
||||||
*/
|
|
||||||
public static void writeUnsignedInt(long value, ByteBuf out) {
|
|
||||||
out.writeByte((int) ((value >> 24) & 0xFF));
|
|
||||||
out.writeByte((int) ((value >> 16) & 0xFF));
|
|
||||||
out.writeByte((int) ((value >> 8) & 0xFF));
|
|
||||||
out.writeByte((int) ((value & 0xFF)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the variable-length padding length field from the payload.
|
|
||||||
*/
|
|
||||||
public static int readPaddingLength(Http2Flags flags, ByteBuf payload) {
|
|
||||||
int paddingLength = 0;
|
|
||||||
if (flags.isPadHighPresent()) {
|
|
||||||
paddingLength += payload.readUnsignedByte() * 256;
|
|
||||||
}
|
}
|
||||||
if (flags.isPadLowPresent()) {
|
|
||||||
paddingLength += payload.readUnsignedByte();
|
|
||||||
}
|
|
||||||
return paddingLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the padding flags in the given flags value as appropriate based on the padding length.
|
* Writes a big-endian (32-bit) unsigned integer to the buffer.
|
||||||
* Returns the new flags value after any padding flags have been set.
|
*/
|
||||||
*/
|
public static void writeUnsignedInt(long value, ByteBuf out) {
|
||||||
public static short setPaddingFlags(short flags, int paddingLength) {
|
out.writeByte((int) ((value >> 24) & 0xFF));
|
||||||
if (paddingLength > 255) {
|
out.writeByte((int) ((value >> 16) & 0xFF));
|
||||||
flags |= Http2FrameCodecUtil.FLAG_PAD_HIGH;
|
out.writeByte((int) ((value >> 8) & 0xFF));
|
||||||
|
out.writeByte((int) ((value & 0xFF)));
|
||||||
}
|
}
|
||||||
if (paddingLength > 0) {
|
|
||||||
flags |= Http2FrameCodecUtil.FLAG_PAD_LOW;
|
|
||||||
}
|
|
||||||
return flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the padding length field to the output buffer.
|
* Reads the variable-length padding length field from the payload.
|
||||||
*/
|
*/
|
||||||
public static void writePaddingLength(int paddingLength, ByteBuf out) {
|
public static int readPaddingLength(Http2Flags flags, ByteBuf payload) {
|
||||||
if (paddingLength > 255) {
|
int paddingLength = 0;
|
||||||
int padHigh = paddingLength / 256;
|
if (flags.isPadHighPresent()) {
|
||||||
out.writeByte(padHigh);
|
paddingLength += payload.readUnsignedByte() * 256;
|
||||||
|
}
|
||||||
|
if (flags.isPadLowPresent()) {
|
||||||
|
paddingLength += payload.readUnsignedByte();
|
||||||
|
}
|
||||||
|
return paddingLength;
|
||||||
}
|
}
|
||||||
if (paddingLength > 0) {
|
|
||||||
int padLow = paddingLength % 256;
|
|
||||||
out.writeByte(padLow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Http2FrameCodecUtil() {
|
/**
|
||||||
}
|
* Sets the padding flags in the given flags value as appropriate based on the padding length.
|
||||||
|
* Returns the new flags value after any padding flags have been set.
|
||||||
|
*/
|
||||||
|
public static short setPaddingFlags(short flags, int paddingLength) {
|
||||||
|
if (paddingLength > 255) {
|
||||||
|
flags |= Http2FrameCodecUtil.FLAG_PAD_HIGH;
|
||||||
|
}
|
||||||
|
if (paddingLength > 0) {
|
||||||
|
flags |= Http2FrameCodecUtil.FLAG_PAD_LOW;
|
||||||
|
}
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the padding length field to the output buffer.
|
||||||
|
*/
|
||||||
|
public static void writePaddingLength(int paddingLength, ByteBuf out) {
|
||||||
|
if (paddingLength > 255) {
|
||||||
|
int padHigh = paddingLength / 256;
|
||||||
|
out.writeByte(padHigh);
|
||||||
|
}
|
||||||
|
if (paddingLength > 0) {
|
||||||
|
int padLow = paddingLength % 256;
|
||||||
|
out.writeByte(padLow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Http2FrameCodecUtil() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,65 +20,65 @@ package io.netty.handler.codec.http2.draft10.frame;
|
|||||||
* Encapsulates the content of an HTTP2 frame header.
|
* Encapsulates the content of an HTTP2 frame header.
|
||||||
*/
|
*/
|
||||||
public final class Http2FrameHeader {
|
public final class Http2FrameHeader {
|
||||||
private final int payloadLength;
|
private final int payloadLength;
|
||||||
private final int type;
|
private final int type;
|
||||||
private final Http2Flags flags;
|
private final Http2Flags flags;
|
||||||
private final int streamId;
|
private final int streamId;
|
||||||
|
|
||||||
private Http2FrameHeader(Builder builder) {
|
private Http2FrameHeader(Builder builder) {
|
||||||
this.payloadLength = builder.payloadLength;
|
this.payloadLength = builder.payloadLength;
|
||||||
this.type = builder.type;
|
this.type = builder.type;
|
||||||
this.flags = builder.flags;
|
this.flags = builder.flags;
|
||||||
this.streamId = builder.streamId;
|
this.streamId = builder.streamId;
|
||||||
}
|
|
||||||
|
|
||||||
public int getPayloadLength() {
|
|
||||||
return payloadLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Http2Flags getFlags() {
|
|
||||||
return flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStreamId() {
|
|
||||||
return streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds instances of {@link Http2FrameHeader}.
|
|
||||||
*/
|
|
||||||
public static class Builder {
|
|
||||||
private int payloadLength;
|
|
||||||
private int type;
|
|
||||||
private Http2Flags flags = new Http2Flags((short) 0);
|
|
||||||
private int streamId;
|
|
||||||
|
|
||||||
public Builder setPayloadLength(int payloadLength) {
|
|
||||||
this.payloadLength = payloadLength;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setType(int type) {
|
public int getPayloadLength() {
|
||||||
this.type = type;
|
return payloadLength;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setFlags(Http2Flags flags) {
|
public int getType() {
|
||||||
this.flags = flags;
|
return type;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setStreamId(int streamId) {
|
public Http2Flags getFlags() {
|
||||||
this.streamId = streamId;
|
return flags;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Http2FrameHeader build() {
|
public int getStreamId() {
|
||||||
return new Http2FrameHeader(this);
|
return streamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds instances of {@link Http2FrameHeader}.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private int payloadLength;
|
||||||
|
private int type;
|
||||||
|
private Http2Flags flags = new Http2Flags((short) 0);
|
||||||
|
private int streamId;
|
||||||
|
|
||||||
|
public Builder setPayloadLength(int payloadLength) {
|
||||||
|
this.payloadLength = payloadLength;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setType(int type) {
|
||||||
|
this.type = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setFlags(Http2Flags flags) {
|
||||||
|
this.flags = flags;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setStreamId(int streamId) {
|
||||||
|
this.streamId = streamId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Http2FrameHeader build() {
|
||||||
|
return new Http2FrameHeader(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -23,38 +23,38 @@ import io.netty.buffer.ByteBufHolder;
|
|||||||
* connection.
|
* connection.
|
||||||
*/
|
*/
|
||||||
public interface Http2GoAwayFrame extends Http2Frame, ByteBufHolder {
|
public interface Http2GoAwayFrame extends Http2Frame, ByteBufHolder {
|
||||||
/**
|
/**
|
||||||
* The highest numbered stream identifier for which the sender of the GOAWAY frame has received
|
* The highest numbered stream identifier for which the sender of the GOAWAY frame has received
|
||||||
* frames on and might have taken some action on.
|
* frames on and might have taken some action on.
|
||||||
*/
|
*/
|
||||||
int getLastStreamId();
|
int getLastStreamId();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The error code containing the reason for closing the connection.
|
* The error code containing the reason for closing the connection.
|
||||||
*/
|
*/
|
||||||
long getErrorCode();
|
long getErrorCode();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the debug data.
|
* Returns the debug data.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
ByteBuf content();
|
ByteBuf content();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2GoAwayFrame copy();
|
Http2GoAwayFrame copy();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2GoAwayFrame duplicate();
|
Http2GoAwayFrame duplicate();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2GoAwayFrame retain();
|
Http2GoAwayFrame retain();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2GoAwayFrame retain(int increment);
|
Http2GoAwayFrame retain(int increment);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2GoAwayFrame touch();
|
Http2GoAwayFrame touch();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2GoAwayFrame touch(Object hint);
|
Http2GoAwayFrame touch(Object hint);
|
||||||
}
|
}
|
||||||
|
@ -22,13 +22,13 @@ import io.netty.handler.codec.http2.draft10.Http2Headers;
|
|||||||
*/
|
*/
|
||||||
public interface Http2HeadersFrame extends Http2StreamFrame {
|
public interface Http2HeadersFrame extends Http2StreamFrame {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the priority of the stream being created.
|
* Gets the priority of the stream being created.
|
||||||
*/
|
*/
|
||||||
int getPriority();
|
int getPriority();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the decoded HTTP headers.
|
* Gets the decoded HTTP headers.
|
||||||
*/
|
*/
|
||||||
Http2Headers getHeaders();
|
Http2Headers getHeaders();
|
||||||
}
|
}
|
||||||
|
@ -22,32 +22,32 @@ import io.netty.buffer.ByteBufHolder;
|
|||||||
* An HTTP2 connection PING frame.
|
* An HTTP2 connection PING frame.
|
||||||
*/
|
*/
|
||||||
public interface Http2PingFrame extends Http2Frame, ByteBufHolder {
|
public interface Http2PingFrame extends Http2Frame, ByteBufHolder {
|
||||||
/**
|
/**
|
||||||
* Indicates whether this frame is an acknowledgment of a PING sent by the peer.
|
* Indicates whether this frame is an acknowledgment of a PING sent by the peer.
|
||||||
*/
|
*/
|
||||||
boolean isAck();
|
boolean isAck();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the opaque data of this frame.
|
* Returns the opaque data of this frame.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
ByteBuf content();
|
ByteBuf content();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2PingFrame copy();
|
Http2PingFrame copy();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2PingFrame duplicate();
|
Http2PingFrame duplicate();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2PingFrame retain();
|
Http2PingFrame retain();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2PingFrame retain(int increment);
|
Http2PingFrame retain(int increment);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2PingFrame touch();
|
Http2PingFrame touch();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Http2PingFrame touch(Object hint);
|
Http2PingFrame touch(Object hint);
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,8 @@ package io.netty.handler.codec.http2.draft10.frame;
|
|||||||
* An HTTP2 priority frame indicating the sender-advised priority for the stream.
|
* An HTTP2 priority frame indicating the sender-advised priority for the stream.
|
||||||
*/
|
*/
|
||||||
public interface Http2PriorityFrame extends Http2StreamFrame {
|
public interface Http2PriorityFrame extends Http2StreamFrame {
|
||||||
/**
|
/**
|
||||||
* The advised priority for the stream.
|
* The advised priority for the stream.
|
||||||
*/
|
*/
|
||||||
int getPriority();
|
int getPriority();
|
||||||
}
|
}
|
||||||
|
@ -22,13 +22,13 @@ import io.netty.handler.codec.http2.draft10.Http2Headers;
|
|||||||
*/
|
*/
|
||||||
public interface Http2PushPromiseFrame extends Http2StreamFrame {
|
public interface Http2PushPromiseFrame extends Http2StreamFrame {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the stream that the endpoint intends to start sending frames for.
|
* The ID of the stream that the endpoint intends to start sending frames for.
|
||||||
*/
|
*/
|
||||||
int getPromisedStreamId();
|
int getPromisedStreamId();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the decoded HTTP headers.
|
* Gets the decoded HTTP headers.
|
||||||
*/
|
*/
|
||||||
Http2Headers getHeaders();
|
Http2Headers getHeaders();
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,8 @@ package io.netty.handler.codec.http2.draft10.frame;
|
|||||||
* HTTP2 RST_STREAM frame that indicates abnormal termination of a stream.
|
* HTTP2 RST_STREAM frame that indicates abnormal termination of a stream.
|
||||||
*/
|
*/
|
||||||
public interface Http2RstStreamFrame extends Http2StreamFrame {
|
public interface Http2RstStreamFrame extends Http2StreamFrame {
|
||||||
/**
|
/**
|
||||||
* The error code containing the reason for the stream being terminated.
|
* The error code containing the reason for the stream being terminated.
|
||||||
*/
|
*/
|
||||||
long getErrorCode();
|
long getErrorCode();
|
||||||
}
|
}
|
||||||
|
@ -17,35 +17,34 @@ package io.netty.handler.codec.http2.draft10.frame;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP2 SETTINGS frame providing configuration parameters that affect how endpoints communicate.
|
* HTTP2 SETTINGS frame providing configuration parameters that affect how endpoints communicate.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public interface Http2SettingsFrame extends Http2Frame {
|
public interface Http2SettingsFrame extends Http2Frame {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether this is an acknowledgment of the settings sent by the peer.
|
* Indicates whether this is an acknowledgment of the settings sent by the peer.
|
||||||
*/
|
*/
|
||||||
boolean isAck();
|
boolean isAck();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the sender's header compression table size, or {@code null} if not set.
|
* Gets the sender's header compression table size, or {@code null} if not set.
|
||||||
*/
|
*/
|
||||||
Integer getHeaderTableSize();
|
Integer getHeaderTableSize();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets whether or not the sender allows server push, or {@code null} if not set.
|
* Gets whether or not the sender allows server push, or {@code null} if not set.
|
||||||
*/
|
*/
|
||||||
Boolean getPushEnabled();
|
Boolean getPushEnabled();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the maximum number of streams the receiver is allowed to create, or {@code null} if not
|
* Gets the maximum number of streams the receiver is allowed to create, or {@code null} if not
|
||||||
* set.
|
* set.
|
||||||
*/
|
*/
|
||||||
Long getMaxConcurrentStreams();
|
Long getMaxConcurrentStreams();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the sender's initial flow control window in bytes, or {@code null} if not set.
|
* Gets the sender's initial flow control window in bytes, or {@code null} if not set.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
Integer getInitialWindowSize();
|
Integer getInitialWindowSize();
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,13 @@ package io.netty.handler.codec.http2.draft10.frame;
|
|||||||
* Base interface for all frames that are associated to a stream.
|
* Base interface for all frames that are associated to a stream.
|
||||||
*/
|
*/
|
||||||
public interface Http2StreamFrame extends Http2Frame {
|
public interface Http2StreamFrame extends Http2Frame {
|
||||||
/**
|
/**
|
||||||
* Gets the identifier of the associated stream.
|
* Gets the identifier of the associated stream.
|
||||||
*/
|
*/
|
||||||
int getStreamId();
|
int getStreamId();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether this frame represents the last frame for the stream.
|
* Indicates whether this frame represents the last frame for the stream.
|
||||||
*/
|
*/
|
||||||
boolean isEndOfStream();
|
boolean isEndOfStream();
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,9 @@ package io.netty.handler.codec.http2.draft10.frame;
|
|||||||
* HTTP2 WINDOW_UPDATE frame used to implement flow control.
|
* HTTP2 WINDOW_UPDATE frame used to implement flow control.
|
||||||
*/
|
*/
|
||||||
public interface Http2WindowUpdateFrame extends Http2StreamFrame {
|
public interface Http2WindowUpdateFrame extends Http2StreamFrame {
|
||||||
/**
|
/**
|
||||||
* Gets the number of bytes that the sender can transmit in addition to the existing flow control
|
* Gets the number of bytes that the sender can transmit in addition to the existing flow control
|
||||||
* window.
|
* window.
|
||||||
*/
|
*/
|
||||||
int getWindowSizeIncrement();
|
int getWindowSizeIncrement();
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_CONTINUATION;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_CONTINUATION;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readPaddingLength;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readPaddingLength;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -28,98 +29,98 @@ import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
|
|||||||
|
|
||||||
public abstract class AbstractHeadersUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
public abstract class AbstractHeadersUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder for a headers/push_promise frame.
|
* A builder for a headers/push_promise frame.
|
||||||
*/
|
*/
|
||||||
protected abstract class FrameBuilder {
|
protected abstract class FrameBuilder {
|
||||||
protected ByteBuf headerBlock;
|
protected ByteBuf headerBlock;
|
||||||
|
|
||||||
abstract int getStreamId();
|
abstract int getStreamId();
|
||||||
|
|
||||||
final void addHeaderFragment(ByteBuf fragment, ByteBufAllocator alloc) {
|
final void addHeaderFragment(ByteBuf fragment, ByteBufAllocator alloc) {
|
||||||
if (headerBlock == null) {
|
if (headerBlock == null) {
|
||||||
headerBlock = alloc.buffer(fragment.readableBytes());
|
headerBlock = alloc.buffer(fragment.readableBytes());
|
||||||
headerBlock.writeBytes(fragment);
|
headerBlock.writeBytes(fragment);
|
||||||
} else {
|
} else {
|
||||||
ByteBuf buf = alloc.buffer(headerBlock.readableBytes() + fragment.readableBytes());
|
ByteBuf buf = alloc.buffer(headerBlock.readableBytes() + fragment.readableBytes());
|
||||||
buf.writeBytes(headerBlock);
|
buf.writeBytes(headerBlock);
|
||||||
buf.writeBytes(fragment);
|
buf.writeBytes(fragment);
|
||||||
headerBlock.release();
|
headerBlock.release();
|
||||||
headerBlock = buf;
|
headerBlock = buf;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract Http2Frame buildFrame() throws Http2Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract Http2Frame buildFrame() throws Http2Exception;
|
private FrameBuilder frameBuilder;
|
||||||
}
|
|
||||||
|
|
||||||
private FrameBuilder frameBuilder;
|
@Override
|
||||||
|
protected final void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
||||||
|
if (frameBuilder == null) {
|
||||||
|
// This frame is the beginning of a headers/push_promise.
|
||||||
|
validateStartOfHeaderBlock(frameHeader);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
// Validate the continuation of a headers block.
|
||||||
protected final void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
if (frameHeader.getType() != FRAME_TYPE_CONTINUATION) {
|
||||||
if (frameBuilder == null) {
|
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
||||||
// This frame is the beginning of a headers/push_promise.
|
}
|
||||||
validateStartOfHeaderBlock(frameHeader);
|
if (frameBuilder.getStreamId() != frameHeader.getStreamId()) {
|
||||||
return;
|
throw protocolError("Continuation received for wrong stream. Expected %d, found %d",
|
||||||
|
frameBuilder.getStreamId(), frameHeader.getStreamId());
|
||||||
|
}
|
||||||
|
Http2Flags flags = frameHeader.getFlags();
|
||||||
|
if (!flags.isPaddingLengthValid()) {
|
||||||
|
throw protocolError("Pad high is set but pad low is not");
|
||||||
|
}
|
||||||
|
if (frameHeader.getPayloadLength() < flags.getNumPaddingLengthBytes()) {
|
||||||
|
throw protocolError("Frame length %d to small.", frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
|
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
||||||
|
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the continuation of a headers block.
|
@Override
|
||||||
if (frameHeader.getType() != FRAME_TYPE_CONTINUATION) {
|
protected final Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
||||||
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
ByteBufAllocator alloc) throws Http2Exception {
|
||||||
}
|
Http2Flags flags = header.getFlags();
|
||||||
if (frameBuilder.getStreamId() != frameHeader.getStreamId()) {
|
if (frameBuilder == null) {
|
||||||
throw protocolError("Continuation received for wrong stream. Expected %d, found %d",
|
// This is the start of a headers/push_promise frame. Delegate to the subclass to create
|
||||||
frameBuilder.getStreamId(), frameHeader.getStreamId());
|
// the appropriate builder for the frame.
|
||||||
}
|
frameBuilder = createFrameBuilder(header, payload, alloc);
|
||||||
Http2Flags flags = frameHeader.getFlags();
|
} else {
|
||||||
if (!flags.isPaddingLengthValid()) {
|
// Processing a continuation frame for a headers/push_promise. Update the current frame
|
||||||
throw protocolError("Pad high is set but pad low is not");
|
// builder with the new fragment.
|
||||||
}
|
|
||||||
if (frameHeader.getPayloadLength() < flags.getNumPaddingLengthBytes()) {
|
|
||||||
throw protocolError("Frame length %d to small.", frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
|
||||||
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
int paddingLength = readPaddingLength(flags, payload);
|
||||||
protected final Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
|
||||||
ByteBufAllocator alloc) throws Http2Exception {
|
|
||||||
Http2Flags flags = header.getFlags();
|
|
||||||
if (frameBuilder == null) {
|
|
||||||
// This is the start of a headers/push_promise frame. Delegate to the subclass to create
|
|
||||||
// the appropriate builder for the frame.
|
|
||||||
frameBuilder = createFrameBuilder(header, payload, alloc);
|
|
||||||
} else {
|
|
||||||
// Processing a continuation frame for a headers/push_promise. Update the current frame
|
|
||||||
// builder with the new fragment.
|
|
||||||
|
|
||||||
int paddingLength = readPaddingLength(flags, payload);
|
// Determine how much data there is to read by removing the trailing
|
||||||
|
// padding.
|
||||||
|
int dataLength = payload.readableBytes() - paddingLength;
|
||||||
|
if (dataLength < 0) {
|
||||||
|
throw protocolError("Payload too small for padding.");
|
||||||
|
}
|
||||||
|
|
||||||
// Determine how much data there is to read by removing the trailing
|
// The remainder of this frame is the headers block.
|
||||||
// padding.
|
frameBuilder.addHeaderFragment(payload, alloc);
|
||||||
int dataLength = payload.readableBytes() - paddingLength;
|
}
|
||||||
if (dataLength < 0) {
|
|
||||||
throw protocolError("Payload too small for padding.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// The remainder of this frame is the headers block.
|
// If the headers are complete, build the frame.
|
||||||
frameBuilder.addHeaderFragment(payload, alloc);
|
Http2Frame frame = null;
|
||||||
|
if (flags.isEndOfHeaders()) {
|
||||||
|
frame = frameBuilder.buildFrame();
|
||||||
|
frameBuilder = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the headers are complete, build the frame.
|
protected abstract void validateStartOfHeaderBlock(Http2FrameHeader frameHeader)
|
||||||
Http2Frame frame = null;
|
throws Http2Exception;
|
||||||
if (flags.isEndOfHeaders()) {
|
|
||||||
frame = frameBuilder.buildFrame();
|
|
||||||
frameBuilder = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return frame;
|
protected abstract FrameBuilder createFrameBuilder(Http2FrameHeader header, ByteBuf payload,
|
||||||
}
|
ByteBufAllocator alloc) throws Http2Exception;
|
||||||
|
|
||||||
protected abstract void validateStartOfHeaderBlock(Http2FrameHeader frameHeader)
|
|
||||||
throws Http2Exception;
|
|
||||||
|
|
||||||
protected abstract FrameBuilder createFrameBuilder(Http2FrameHeader header, ByteBuf payload,
|
|
||||||
ByteBufAllocator alloc) throws Http2Exception;
|
|
||||||
}
|
}
|
||||||
|
@ -25,42 +25,42 @@ import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
|
|||||||
* Abstract base class for all {@link Http2FrameUnmarshaller} classes.
|
* Abstract base class for all {@link Http2FrameUnmarshaller} classes.
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractHttp2FrameUnmarshaller implements Http2FrameUnmarshaller {
|
public abstract class AbstractHttp2FrameUnmarshaller implements Http2FrameUnmarshaller {
|
||||||
private Http2FrameHeader header;
|
private Http2FrameHeader header;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final Http2FrameUnmarshaller unmarshall(Http2FrameHeader header) throws Http2Exception {
|
public final Http2FrameUnmarshaller unmarshall(Http2FrameHeader header) throws Http2Exception {
|
||||||
if (header == null) {
|
if (header == null) {
|
||||||
throw new IllegalArgumentException("header must be non-null.");
|
throw new IllegalArgumentException("header must be non-null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(header);
|
||||||
|
this.header = header;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(header);
|
@Override
|
||||||
this.header = header;
|
public final Http2Frame from(ByteBuf payload, ByteBufAllocator alloc) throws Http2Exception {
|
||||||
return this;
|
if (header == null) {
|
||||||
}
|
throw new IllegalStateException("header must be set before calling from().");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
return doUnmarshall(header, payload, alloc);
|
||||||
public final Http2Frame from(ByteBuf payload, ByteBufAllocator alloc) throws Http2Exception {
|
|
||||||
if (header == null) {
|
|
||||||
throw new IllegalStateException("header must be set before calling from().");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return doUnmarshall(header, payload, alloc);
|
/**
|
||||||
}
|
* Verifies that the given frame header is valid for the frame type(s) supported by this decoder.
|
||||||
|
*/
|
||||||
|
protected abstract void validate(Http2FrameHeader frameHeader) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that the given frame header is valid for the frame type(s) supported by this decoder.
|
* Unmarshalls the frame.
|
||||||
*/
|
*
|
||||||
protected abstract void validate(Http2FrameHeader frameHeader) throws Http2Exception;
|
* @param header the frame header
|
||||||
|
* @param payload the payload of the frame.
|
||||||
/**
|
* @param alloc an allocator for new buffers
|
||||||
* Unmarshalls the frame.
|
* @return the frame
|
||||||
*
|
* @throws Http2Exception thrown if any protocol error was encountered.
|
||||||
* @param header the frame header
|
*/
|
||||||
* @param payload the payload of the frame.
|
protected abstract Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
||||||
* @param alloc an allocator for new buffers
|
ByteBufAllocator alloc) throws Http2Exception;
|
||||||
* @return the frame
|
|
||||||
* @throws Http2Exception thrown if any protocol error was encountered.
|
|
||||||
*/
|
|
||||||
protected abstract Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
|
||||||
ByteBufAllocator alloc) throws Http2Exception;
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package io.netty.handler.codec.http2.draft10.frame.decoder;
|
|||||||
|
|
||||||
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_HEADER_TABLE_SIZE;
|
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_HEADER_TABLE_SIZE;
|
||||||
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_MAX_HEADER_SIZE;
|
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_MAX_HEADER_SIZE;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufInputStream;
|
import io.netty.buffer.ByteBufInputStream;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Error;
|
import io.netty.handler.codec.http2.draft10.Http2Error;
|
||||||
@ -30,34 +31,34 @@ import com.twitter.hpack.HeaderListener;
|
|||||||
|
|
||||||
public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder {
|
public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder {
|
||||||
|
|
||||||
private final Decoder decoder;
|
private final Decoder decoder;
|
||||||
|
|
||||||
public DefaultHttp2HeadersDecoder() {
|
public DefaultHttp2HeadersDecoder() {
|
||||||
this.decoder = new Decoder(DEFAULT_MAX_HEADER_SIZE, DEFAULT_HEADER_TABLE_SIZE);
|
this.decoder = new Decoder(DEFAULT_MAX_HEADER_SIZE, DEFAULT_HEADER_TABLE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setHeaderTableSize(int size) throws Http2Exception {
|
public void setHeaderTableSize(int size) throws Http2Exception {
|
||||||
// TODO: can we throw away the decoder and create a new one?
|
// TODO: can we throw away the decoder and create a new one?
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception {
|
public Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception {
|
||||||
try {
|
try {
|
||||||
final Http2Headers.Builder headersBuilder = new Http2Headers.Builder();
|
final Http2Headers.Builder headersBuilder = new Http2Headers.Builder();
|
||||||
HeaderListener listener = new HeaderListener() {
|
HeaderListener listener = new HeaderListener() {
|
||||||
@Override
|
@Override
|
||||||
public void emitHeader(byte[] key, byte[] value) {
|
public void emitHeader(byte[] key, byte[] value) {
|
||||||
headersBuilder.addHeader(key, value);
|
headersBuilder.addHeader(key, value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
decoder.decode(new ByteBufInputStream(headerBlock), listener);
|
decoder.decode(new ByteBufInputStream(headerBlock), listener);
|
||||||
decoder.endHeaderBlock(listener);
|
decoder.endHeaderBlock(listener);
|
||||||
|
|
||||||
return headersBuilder.build();
|
return headersBuilder.build();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new Http2Exception(Http2Error.COMPRESSION_ERROR, e.getMessage());
|
throw new Http2Exception(Http2Error.COMPRESSION_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_DATA;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_DATA;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readPaddingLength;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readPaddingLength;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -33,54 +34,54 @@ import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
|
|||||||
*/
|
*/
|
||||||
public class Http2DataFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
public class Http2DataFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
||||||
if (frameHeader.getType() != FRAME_TYPE_DATA) {
|
if (frameHeader.getType() != FRAME_TYPE_DATA) {
|
||||||
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
||||||
}
|
}
|
||||||
if (frameHeader.getStreamId() <= 0) {
|
if (frameHeader.getStreamId() <= 0) {
|
||||||
throw protocolError("A stream ID must be > 0.");
|
throw protocolError("A stream ID must be > 0.");
|
||||||
}
|
}
|
||||||
Http2Flags flags = frameHeader.getFlags();
|
Http2Flags flags = frameHeader.getFlags();
|
||||||
if (!flags.isPaddingLengthValid()) {
|
if (!flags.isPaddingLengthValid()) {
|
||||||
throw protocolError("Pad high is set but pad low is not");
|
throw protocolError("Pad high is set but pad low is not");
|
||||||
}
|
}
|
||||||
if (frameHeader.getPayloadLength() < flags.getNumPaddingLengthBytes()) {
|
if (frameHeader.getPayloadLength() < flags.getNumPaddingLengthBytes()) {
|
||||||
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
|
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
|
||||||
}
|
}
|
||||||
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
||||||
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
|
||||||
ByteBufAllocator alloc) throws Http2Exception {
|
|
||||||
DefaultHttp2DataFrame.Builder builder = new DefaultHttp2DataFrame.Builder();
|
|
||||||
builder.setStreamId(header.getStreamId());
|
|
||||||
|
|
||||||
Http2Flags flags = header.getFlags();
|
|
||||||
builder.setEndOfStream(flags.isEndOfStream());
|
|
||||||
|
|
||||||
// Read the padding length.
|
|
||||||
int paddingLength = readPaddingLength(flags, payload);
|
|
||||||
builder.setPaddingLength(paddingLength);
|
|
||||||
|
|
||||||
// Determine how much data there is to read by removing the trailing
|
|
||||||
// padding.
|
|
||||||
int dataLength = payload.readableBytes() - paddingLength;
|
|
||||||
if (dataLength < 0) {
|
|
||||||
throw protocolError("Frame payload too small for padding.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the remaining data into the frame.
|
@Override
|
||||||
ByteBuf data = payload.slice(payload.readerIndex(), dataLength).retain();
|
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
||||||
builder.setContent(data);
|
ByteBufAllocator alloc) throws Http2Exception {
|
||||||
|
DefaultHttp2DataFrame.Builder builder = new DefaultHttp2DataFrame.Builder();
|
||||||
|
builder.setStreamId(header.getStreamId());
|
||||||
|
|
||||||
// Skip the rest of the bytes in the payload.
|
Http2Flags flags = header.getFlags();
|
||||||
payload.skipBytes(payload.readableBytes());
|
builder.setEndOfStream(flags.isEndOfStream());
|
||||||
|
|
||||||
return builder.build();
|
// Read the padding length.
|
||||||
}
|
int paddingLength = readPaddingLength(flags, payload);
|
||||||
|
builder.setPaddingLength(paddingLength);
|
||||||
|
|
||||||
|
// Determine how much data there is to read by removing the trailing
|
||||||
|
// padding.
|
||||||
|
int dataLength = payload.readableBytes() - paddingLength;
|
||||||
|
if (dataLength < 0) {
|
||||||
|
throw protocolError("Frame payload too small for padding.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the remaining data into the frame.
|
||||||
|
ByteBuf data = payload.slice(payload.readerIndex(), dataLength).retain();
|
||||||
|
builder.setContent(data);
|
||||||
|
|
||||||
|
// Skip the rest of the bytes in the payload.
|
||||||
|
payload.skipBytes(payload.readableBytes());
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package io.netty.handler.codec.http2.draft10.frame.decoder;
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_LENGTH_MASK;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_LENGTH_MASK;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
@ -61,24 +62,24 @@ public class Http2FrameDecoder extends ByteToMessageDecoder {
|
|||||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||||
try {
|
try {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case FRAME_HEADER:
|
case FRAME_HEADER:
|
||||||
processFrameHeader(in);
|
processFrameHeader(in);
|
||||||
if (state == State.FRAME_HEADER) {
|
if (state == State.FRAME_HEADER) {
|
||||||
// Still haven't read the entire frame header yet.
|
// Still haven't read the entire frame header yet.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we successfully read the entire frame header, drop down and start processing
|
||||||
|
// the payload now.
|
||||||
|
|
||||||
|
case FRAME_PAYLOAD:
|
||||||
|
processFramePayload(ctx, in, out);
|
||||||
break;
|
break;
|
||||||
}
|
case ERROR:
|
||||||
|
in.skipBytes(in.readableBytes());
|
||||||
// If we successfully read the entire frame header, drop down and start processing
|
break;
|
||||||
// the payload now.
|
default:
|
||||||
|
throw new IllegalStateException("Should never get here");
|
||||||
case FRAME_PAYLOAD:
|
|
||||||
processFramePayload(ctx, in, out);
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
in.skipBytes(in.readableBytes());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Should never get here");
|
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
ctx.fireExceptionCaught(t);
|
ctx.fireExceptionCaught(t);
|
||||||
|
@ -27,24 +27,24 @@ import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
|
|||||||
*/
|
*/
|
||||||
public interface Http2FrameUnmarshaller {
|
public interface Http2FrameUnmarshaller {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares the unmarshaller for the next frame.
|
* Prepares the unmarshaller for the next frame.
|
||||||
*
|
*
|
||||||
* @param header the header providing the detais of the frame to be unmarshalled.
|
* @param header the header providing the detais of the frame to be unmarshalled.
|
||||||
* @return this unmarshaller
|
* @return this unmarshaller
|
||||||
* @throws Http2Exception thrown if any of the information of the header violates the protocol.
|
* @throws Http2Exception thrown if any of the information of the header violates the protocol.
|
||||||
*/
|
*/
|
||||||
Http2FrameUnmarshaller unmarshall(Http2FrameHeader header) throws Http2Exception;
|
Http2FrameUnmarshaller unmarshall(Http2FrameHeader header) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unmarshalls the frame from the payload.
|
* Unmarshalls the frame from the payload.
|
||||||
*
|
*
|
||||||
* @param payload the payload from which the frame is to be unmarshalled.
|
* @param payload the payload from which the frame is to be unmarshalled.
|
||||||
* @param alloc the allocator for any new buffers required by the unmarshaller.
|
* @param alloc the allocator for any new buffers required by the unmarshaller.
|
||||||
* @return the frame or {@code null} if the unmarshall operation is processing is incomplete and
|
* @return the frame or {@code null} if the unmarshall operation is processing is incomplete and
|
||||||
* requires additional data.
|
* requires additional data.
|
||||||
* @throws Http2Exception thrown if any protocol error was encountered while unmarshalling the
|
* @throws Http2Exception thrown if any protocol error was encountered while unmarshalling the
|
||||||
* frame.
|
* frame.
|
||||||
*/
|
*/
|
||||||
Http2Frame from(ByteBuf payload, ByteBufAllocator alloc) throws Http2Exception;
|
Http2Frame from(ByteBuf payload, ByteBufAllocator alloc) throws Http2Exception;
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_GO_AWAY;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_GO_AWAY;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -32,37 +33,37 @@ import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
|
|||||||
*/
|
*/
|
||||||
public class Http2GoAwayFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
public class Http2GoAwayFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
||||||
if (frameHeader.getType() != FRAME_TYPE_GO_AWAY) {
|
if (frameHeader.getType() != FRAME_TYPE_GO_AWAY) {
|
||||||
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
||||||
|
}
|
||||||
|
if (frameHeader.getStreamId() != 0) {
|
||||||
|
throw protocolError("A stream ID must be zero.");
|
||||||
|
}
|
||||||
|
if (frameHeader.getPayloadLength() < 8) {
|
||||||
|
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
|
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
||||||
|
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (frameHeader.getStreamId() != 0) {
|
|
||||||
throw protocolError("A stream ID must be zero.");
|
@Override
|
||||||
|
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
||||||
|
ByteBufAllocator alloc) throws Http2Exception {
|
||||||
|
DefaultHttp2GoAwayFrame.Builder builder = new DefaultHttp2GoAwayFrame.Builder();
|
||||||
|
|
||||||
|
int lastStreamId = readUnsignedInt(payload);
|
||||||
|
builder.setLastStreamId(lastStreamId);
|
||||||
|
|
||||||
|
long errorCode = payload.readUnsignedInt();
|
||||||
|
builder.setErrorCode(errorCode);
|
||||||
|
|
||||||
|
// The remainder of this frame is the debug data.
|
||||||
|
ByteBuf data = payload.slice().retain();
|
||||||
|
builder.setDebugData(data);
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
if (frameHeader.getPayloadLength() < 8) {
|
|
||||||
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
|
||||||
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
|
||||||
ByteBufAllocator alloc) throws Http2Exception {
|
|
||||||
DefaultHttp2GoAwayFrame.Builder builder = new DefaultHttp2GoAwayFrame.Builder();
|
|
||||||
|
|
||||||
int lastStreamId = readUnsignedInt(payload);
|
|
||||||
builder.setLastStreamId(lastStreamId);
|
|
||||||
|
|
||||||
long errorCode = payload.readUnsignedInt();
|
|
||||||
builder.setErrorCode(errorCode);
|
|
||||||
|
|
||||||
// The remainder of this frame is the debug data.
|
|
||||||
ByteBuf data = payload.slice().retain();
|
|
||||||
builder.setDebugData(data);
|
|
||||||
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -24,13 +24,13 @@ import io.netty.handler.codec.http2.draft10.Http2Headers;
|
|||||||
*/
|
*/
|
||||||
public interface Http2HeadersDecoder {
|
public interface Http2HeadersDecoder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes the given headers block and returns the headers.
|
* Decodes the given headers block and returns the headers.
|
||||||
*/
|
*/
|
||||||
Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception;
|
Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the new max header table size for this decoder.
|
* Sets the new max header table size for this decoder.
|
||||||
*/
|
*/
|
||||||
void setHeaderTableSize(int size) throws Http2Exception;
|
void setHeaderTableSize(int size) throws Http2Exception;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRA
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readPaddingLength;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readPaddingLength;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -36,114 +37,114 @@ import com.google.common.base.Preconditions;
|
|||||||
*/
|
*/
|
||||||
public class Http2HeadersFrameUnmarshaller extends AbstractHeadersUnmarshaller {
|
public class Http2HeadersFrameUnmarshaller extends AbstractHeadersUnmarshaller {
|
||||||
|
|
||||||
private final Http2HeadersDecoder headersDecoder;
|
private final Http2HeadersDecoder headersDecoder;
|
||||||
|
|
||||||
public Http2HeadersFrameUnmarshaller(Http2HeadersDecoder headersDecoder) {
|
public Http2HeadersFrameUnmarshaller(Http2HeadersDecoder headersDecoder) {
|
||||||
this.headersDecoder = Preconditions.checkNotNull(headersDecoder, "headersDecoder");
|
this.headersDecoder = Preconditions.checkNotNull(headersDecoder, "headersDecoder");
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void validateStartOfHeaderBlock(Http2FrameHeader frameHeader) throws Http2Exception {
|
|
||||||
if (frameHeader.getType() != FRAME_TYPE_HEADERS) {
|
|
||||||
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frameHeader.getStreamId() <= 0) {
|
@Override
|
||||||
throw protocolError("A stream ID must > 0.");
|
protected void validateStartOfHeaderBlock(Http2FrameHeader frameHeader) throws Http2Exception {
|
||||||
}
|
if (frameHeader.getType() != FRAME_TYPE_HEADERS) {
|
||||||
|
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
||||||
Http2Flags flags = frameHeader.getFlags();
|
|
||||||
if (flags.isPriorityPresent() && frameHeader.getPayloadLength() < 4) {
|
|
||||||
throw protocolError("Frame length too small." + frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!flags.isPaddingLengthValid()) {
|
|
||||||
throw protocolError("Pad high is set but pad low is not");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frameHeader.getPayloadLength() < flags.getNumPaddingLengthBytes()) {
|
|
||||||
throw protocolError("Frame length %d too small for padding.", frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
|
||||||
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected FrameBuilder createFrameBuilder(final Http2FrameHeader header, final ByteBuf payload,
|
|
||||||
ByteBufAllocator alloc) throws Http2Exception {
|
|
||||||
try {
|
|
||||||
final DefaultHttp2HeadersFrame.Builder builder = new DefaultHttp2HeadersFrame.Builder();
|
|
||||||
builder.setStreamId(header.getStreamId());
|
|
||||||
|
|
||||||
Http2Flags flags = header.getFlags();
|
|
||||||
builder.setEndOfStream(flags.isEndOfStream());
|
|
||||||
|
|
||||||
// Read the padding length.
|
|
||||||
int paddingLength = readPaddingLength(flags, payload);
|
|
||||||
|
|
||||||
// Read the priority if it was included in the frame.
|
|
||||||
if (flags.isPriorityPresent()) {
|
|
||||||
int priority = readUnsignedInt(payload);
|
|
||||||
builder.setPriority(priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine how much data there is to read by removing the trailing
|
|
||||||
// padding.
|
|
||||||
int dataLength = payload.readableBytes() - paddingLength;
|
|
||||||
if (dataLength < 0) {
|
|
||||||
throw protocolError("Payload too small for padding");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a view of the header block portion of the payload.
|
|
||||||
final ByteBuf headerSlice = payload.readSlice(dataLength);
|
|
||||||
|
|
||||||
// The remainder of this frame is the headers block.
|
|
||||||
if (flags.isEndOfHeaders()) {
|
|
||||||
// Optimization: don't copy the buffer if we have the entire headers block.
|
|
||||||
return new FrameBuilder() {
|
|
||||||
@Override
|
|
||||||
int getStreamId() {
|
|
||||||
return header.getStreamId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Http2Frame buildFrame() throws Http2Exception {
|
|
||||||
Http2Headers headers = headersDecoder.decodeHeaders(headerSlice);
|
|
||||||
builder.setHeaders(headers);
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// The header block is not complete. Await one or more continuation frames
|
|
||||||
// to complete the block before decoding.
|
|
||||||
FrameBuilder frameBuilder = new FrameBuilder() {
|
|
||||||
@Override
|
|
||||||
int getStreamId() {
|
|
||||||
return header.getStreamId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
if (frameHeader.getStreamId() <= 0) {
|
||||||
Http2Frame buildFrame() throws Http2Exception {
|
throw protocolError("A stream ID must > 0.");
|
||||||
try {
|
|
||||||
Http2Headers headers = headersDecoder.decodeHeaders(headerBlock);
|
|
||||||
builder.setHeaders(headers);
|
|
||||||
return builder.build();
|
|
||||||
} finally {
|
|
||||||
headerBlock.release();
|
|
||||||
headerBlock = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Copy and add the initial fragment of the header block.
|
Http2Flags flags = frameHeader.getFlags();
|
||||||
frameBuilder.addHeaderFragment(headerSlice, alloc);
|
if (flags.isPriorityPresent() && frameHeader.getPayloadLength() < 4) {
|
||||||
|
throw protocolError("Frame length too small." + frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
|
|
||||||
return frameBuilder;
|
if (!flags.isPaddingLengthValid()) {
|
||||||
} finally {
|
throw protocolError("Pad high is set but pad low is not");
|
||||||
payload.skipBytes(payload.readableBytes());
|
}
|
||||||
|
|
||||||
|
if (frameHeader.getPayloadLength() < flags.getNumPaddingLengthBytes()) {
|
||||||
|
throw protocolError("Frame length %d too small for padding.", frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
||||||
|
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected FrameBuilder createFrameBuilder(final Http2FrameHeader header, final ByteBuf payload,
|
||||||
|
ByteBufAllocator alloc) throws Http2Exception {
|
||||||
|
try {
|
||||||
|
final DefaultHttp2HeadersFrame.Builder builder = new DefaultHttp2HeadersFrame.Builder();
|
||||||
|
builder.setStreamId(header.getStreamId());
|
||||||
|
|
||||||
|
Http2Flags flags = header.getFlags();
|
||||||
|
builder.setEndOfStream(flags.isEndOfStream());
|
||||||
|
|
||||||
|
// Read the padding length.
|
||||||
|
int paddingLength = readPaddingLength(flags, payload);
|
||||||
|
|
||||||
|
// Read the priority if it was included in the frame.
|
||||||
|
if (flags.isPriorityPresent()) {
|
||||||
|
int priority = readUnsignedInt(payload);
|
||||||
|
builder.setPriority(priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine how much data there is to read by removing the trailing
|
||||||
|
// padding.
|
||||||
|
int dataLength = payload.readableBytes() - paddingLength;
|
||||||
|
if (dataLength < 0) {
|
||||||
|
throw protocolError("Payload too small for padding");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a view of the header block portion of the payload.
|
||||||
|
final ByteBuf headerSlice = payload.readSlice(dataLength);
|
||||||
|
|
||||||
|
// The remainder of this frame is the headers block.
|
||||||
|
if (flags.isEndOfHeaders()) {
|
||||||
|
// Optimization: don't copy the buffer if we have the entire headers block.
|
||||||
|
return new FrameBuilder() {
|
||||||
|
@Override
|
||||||
|
int getStreamId() {
|
||||||
|
return header.getStreamId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Http2Frame buildFrame() throws Http2Exception {
|
||||||
|
Http2Headers headers = headersDecoder.decodeHeaders(headerSlice);
|
||||||
|
builder.setHeaders(headers);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// The header block is not complete. Await one or more continuation frames
|
||||||
|
// to complete the block before decoding.
|
||||||
|
FrameBuilder frameBuilder = new FrameBuilder() {
|
||||||
|
@Override
|
||||||
|
int getStreamId() {
|
||||||
|
return header.getStreamId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Http2Frame buildFrame() throws Http2Exception {
|
||||||
|
try {
|
||||||
|
Http2Headers headers = headersDecoder.decodeHeaders(headerBlock);
|
||||||
|
builder.setHeaders(headers);
|
||||||
|
return builder.build();
|
||||||
|
} finally {
|
||||||
|
headerBlock.release();
|
||||||
|
headerBlock = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy and add the initial fragment of the header block.
|
||||||
|
frameBuilder.addHeaderFragment(headerSlice, alloc);
|
||||||
|
|
||||||
|
return frameBuilder;
|
||||||
|
} finally {
|
||||||
|
payload.skipBytes(payload.readableBytes());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package io.netty.handler.codec.http2.draft10.frame.decoder;
|
|||||||
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PING;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PING;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.PING_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.PING_FRAME_PAYLOAD_LENGTH;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -31,30 +32,30 @@ import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
|
|||||||
*/
|
*/
|
||||||
public class Http2PingFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
public class Http2PingFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
||||||
if (frameHeader.getType() != FRAME_TYPE_PING) {
|
if (frameHeader.getType() != FRAME_TYPE_PING) {
|
||||||
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
||||||
|
}
|
||||||
|
if (frameHeader.getStreamId() != 0) {
|
||||||
|
throw protocolError("A stream ID must be zero.");
|
||||||
|
}
|
||||||
|
if (frameHeader.getPayloadLength() != PING_FRAME_PAYLOAD_LENGTH) {
|
||||||
|
throw protocolError("Frame length %d incorrect size for ping.",
|
||||||
|
frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (frameHeader.getStreamId() != 0) {
|
|
||||||
throw protocolError("A stream ID must be zero.");
|
@Override
|
||||||
|
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
||||||
|
ByteBufAllocator alloc) throws Http2Exception {
|
||||||
|
DefaultHttp2PingFrame.Builder builder = new DefaultHttp2PingFrame.Builder();
|
||||||
|
builder.setAck(header.getFlags().isAck());
|
||||||
|
|
||||||
|
// The remainder of this frame is the opaque data.
|
||||||
|
ByteBuf data = payload.slice().retain();
|
||||||
|
builder.setData(data);
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
if (frameHeader.getPayloadLength() != PING_FRAME_PAYLOAD_LENGTH) {
|
|
||||||
throw protocolError("Frame length %d incorrect size for ping.",
|
|
||||||
frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
|
||||||
ByteBufAllocator alloc) throws Http2Exception {
|
|
||||||
DefaultHttp2PingFrame.Builder builder = new DefaultHttp2PingFrame.Builder();
|
|
||||||
builder.setAck(header.getFlags().isAck());
|
|
||||||
|
|
||||||
// The remainder of this frame is the opaque data.
|
|
||||||
ByteBuf data = payload.slice().retain();
|
|
||||||
builder.setData(data);
|
|
||||||
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PRIORITY;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PRIORITY;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -31,31 +32,31 @@ import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
|
|||||||
*/
|
*/
|
||||||
public class Http2PriorityFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
public class Http2PriorityFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
||||||
if (frameHeader.getType() != FRAME_TYPE_PRIORITY) {
|
if (frameHeader.getType() != FRAME_TYPE_PRIORITY) {
|
||||||
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
||||||
|
}
|
||||||
|
if (frameHeader.getStreamId() <= 0) {
|
||||||
|
throw protocolError("A stream ID must be > 0.");
|
||||||
|
}
|
||||||
|
if (frameHeader.getPayloadLength() < 4) {
|
||||||
|
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
|
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
||||||
|
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (frameHeader.getStreamId() <= 0) {
|
|
||||||
throw protocolError("A stream ID must be > 0.");
|
|
||||||
}
|
|
||||||
if (frameHeader.getPayloadLength() < 4) {
|
|
||||||
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
|
||||||
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
||||||
ByteBufAllocator alloc) throws Http2Exception {
|
ByteBufAllocator alloc) throws Http2Exception {
|
||||||
DefaultHttp2PriorityFrame.Builder builder = new DefaultHttp2PriorityFrame.Builder();
|
DefaultHttp2PriorityFrame.Builder builder = new DefaultHttp2PriorityFrame.Builder();
|
||||||
builder.setStreamId(header.getStreamId());
|
builder.setStreamId(header.getStreamId());
|
||||||
|
|
||||||
int priority = readUnsignedInt(payload);
|
int priority = readUnsignedInt(payload);
|
||||||
builder.setPriority(priority);
|
builder.setPriority(priority);
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PUSH_PROMISE;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PUSH_PROMISE;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -35,82 +36,82 @@ import com.google.common.base.Preconditions;
|
|||||||
*/
|
*/
|
||||||
public class Http2PushPromiseFrameUnmarshaller extends AbstractHeadersUnmarshaller {
|
public class Http2PushPromiseFrameUnmarshaller extends AbstractHeadersUnmarshaller {
|
||||||
|
|
||||||
private final Http2HeadersDecoder headersDecoder;
|
private final Http2HeadersDecoder headersDecoder;
|
||||||
|
|
||||||
public Http2PushPromiseFrameUnmarshaller(Http2HeadersDecoder headersDecoder) {
|
public Http2PushPromiseFrameUnmarshaller(Http2HeadersDecoder headersDecoder) {
|
||||||
this.headersDecoder = Preconditions.checkNotNull(headersDecoder, "headersDecoder");
|
this.headersDecoder = Preconditions.checkNotNull(headersDecoder, "headersDecoder");
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void validateStartOfHeaderBlock(Http2FrameHeader frameHeader) throws Http2Exception {
|
|
||||||
if (frameHeader.getType() != FRAME_TYPE_PUSH_PROMISE) {
|
|
||||||
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
|
||||||
}
|
}
|
||||||
if (frameHeader.getStreamId() <= 0) {
|
|
||||||
throw protocolError("A stream ID must > 0.");
|
@Override
|
||||||
|
protected void validateStartOfHeaderBlock(Http2FrameHeader frameHeader) throws Http2Exception {
|
||||||
|
if (frameHeader.getType() != FRAME_TYPE_PUSH_PROMISE) {
|
||||||
|
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
||||||
|
}
|
||||||
|
if (frameHeader.getStreamId() <= 0) {
|
||||||
|
throw protocolError("A stream ID must > 0.");
|
||||||
|
}
|
||||||
|
if (frameHeader.getPayloadLength() < 4) {
|
||||||
|
throw protocolError("Frame length too small." + frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
|
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
||||||
|
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (frameHeader.getPayloadLength() < 4) {
|
|
||||||
throw protocolError("Frame length too small." + frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
|
||||||
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected FrameBuilder createFrameBuilder(final Http2FrameHeader header, ByteBuf payload,
|
protected FrameBuilder createFrameBuilder(final Http2FrameHeader header, ByteBuf payload,
|
||||||
ByteBufAllocator alloc) throws Http2Exception {
|
ByteBufAllocator alloc) throws Http2Exception {
|
||||||
final DefaultHttp2PushPromiseFrame.Builder builder = new DefaultHttp2PushPromiseFrame.Builder();
|
final DefaultHttp2PushPromiseFrame.Builder builder = new DefaultHttp2PushPromiseFrame.Builder();
|
||||||
builder.setStreamId(header.getStreamId());
|
builder.setStreamId(header.getStreamId());
|
||||||
|
|
||||||
int promisedStreamId = readUnsignedInt(payload);
|
int promisedStreamId = readUnsignedInt(payload);
|
||||||
builder.setPromisedStreamId(promisedStreamId);
|
builder.setPromisedStreamId(promisedStreamId);
|
||||||
|
|
||||||
final ByteBuf headerSlice = payload.readSlice(payload.readableBytes());
|
final ByteBuf headerSlice = payload.readSlice(payload.readableBytes());
|
||||||
|
|
||||||
// The remainder of this frame is the headers block.
|
// The remainder of this frame is the headers block.
|
||||||
Http2Flags flags = header.getFlags();
|
Http2Flags flags = header.getFlags();
|
||||||
if (flags.isEndOfHeaders()) {
|
if (flags.isEndOfHeaders()) {
|
||||||
// Optimization: don't copy the buffer if we have the entire headers block.
|
// Optimization: don't copy the buffer if we have the entire headers block.
|
||||||
return new FrameBuilder() {
|
return new FrameBuilder() {
|
||||||
@Override
|
@Override
|
||||||
int getStreamId() {
|
int getStreamId() {
|
||||||
return header.getStreamId();
|
return header.getStreamId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Http2Frame buildFrame() throws Http2Exception {
|
||||||
|
Http2Headers headers = headersDecoder.decodeHeaders(headerSlice);
|
||||||
|
builder.setHeaders(headers);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// The header block is not complete. Await one or more continuation frames
|
||||||
Http2Frame buildFrame() throws Http2Exception {
|
// to complete the block before decoding.
|
||||||
Http2Headers headers = headersDecoder.decodeHeaders(headerSlice);
|
FrameBuilder frameBuilder = new FrameBuilder() {
|
||||||
builder.setHeaders(headers);
|
@Override
|
||||||
return builder.build();
|
int getStreamId() {
|
||||||
}
|
return header.getStreamId();
|
||||||
};
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Http2Frame buildFrame() throws Http2Exception {
|
||||||
|
try {
|
||||||
|
Http2Headers headers = headersDecoder.decodeHeaders(headerBlock);
|
||||||
|
builder.setHeaders(headers);
|
||||||
|
return builder.build();
|
||||||
|
} finally {
|
||||||
|
headerBlock.release();
|
||||||
|
headerBlock = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy and add the initial fragment of the header block.
|
||||||
|
frameBuilder.addHeaderFragment(headerSlice, alloc);
|
||||||
|
|
||||||
|
return frameBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The header block is not complete. Await one or more continuation frames
|
|
||||||
// to complete the block before decoding.
|
|
||||||
FrameBuilder frameBuilder = new FrameBuilder() {
|
|
||||||
@Override
|
|
||||||
int getStreamId() {
|
|
||||||
return header.getStreamId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Http2Frame buildFrame() throws Http2Exception {
|
|
||||||
try {
|
|
||||||
Http2Headers headers = headersDecoder.decodeHeaders(headerBlock);
|
|
||||||
builder.setHeaders(headers);
|
|
||||||
return builder.build();
|
|
||||||
} finally {
|
|
||||||
headerBlock.release();
|
|
||||||
headerBlock = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Copy and add the initial fragment of the header block.
|
|
||||||
frameBuilder.addHeaderFragment(headerSlice, alloc);
|
|
||||||
|
|
||||||
return frameBuilder;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package io.netty.handler.codec.http2.draft10.frame.decoder;
|
|||||||
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_RST_STREAM;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_RST_STREAM;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -30,32 +31,32 @@ import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
|
|||||||
*/
|
*/
|
||||||
public class Http2RstStreamFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
public class Http2RstStreamFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
||||||
if (frameHeader.getType() != FRAME_TYPE_RST_STREAM) {
|
if (frameHeader.getType() != FRAME_TYPE_RST_STREAM) {
|
||||||
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
||||||
|
}
|
||||||
|
if (frameHeader.getStreamId() <= 0) {
|
||||||
|
throw protocolError("A stream ID must be > 0.");
|
||||||
|
}
|
||||||
|
if (frameHeader.getPayloadLength() < 4) {
|
||||||
|
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
|
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
||||||
|
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (frameHeader.getStreamId() <= 0) {
|
|
||||||
throw protocolError("A stream ID must be > 0.");
|
|
||||||
}
|
|
||||||
if (frameHeader.getPayloadLength() < 4) {
|
|
||||||
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
|
||||||
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
||||||
ByteBufAllocator alloc) throws Http2Exception {
|
ByteBufAllocator alloc) throws Http2Exception {
|
||||||
DefaultHttp2RstStreamFrame.Builder builder = new DefaultHttp2RstStreamFrame.Builder();
|
DefaultHttp2RstStreamFrame.Builder builder = new DefaultHttp2RstStreamFrame.Builder();
|
||||||
builder.setStreamId(header.getStreamId());
|
builder.setStreamId(header.getStreamId());
|
||||||
|
|
||||||
long errorCode = payload.readUnsignedInt();
|
long errorCode = payload.readUnsignedInt();
|
||||||
builder.setErrorCode(errorCode);
|
builder.setErrorCode(errorCode);
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SET
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_HEADER_TABLE_SIZE;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_HEADER_TABLE_SIZE;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_INITIAL_WINDOW_SIZE;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_INITIAL_WINDOW_SIZE;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_MAX_CONCURRENT_STREAMS;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_MAX_CONCURRENT_STREAMS;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -34,65 +35,65 @@ import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
|
|||||||
*/
|
*/
|
||||||
public class Http2SettingsFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
public class Http2SettingsFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
||||||
if (frameHeader.getType() != FRAME_TYPE_SETTINGS) {
|
if (frameHeader.getType() != FRAME_TYPE_SETTINGS) {
|
||||||
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
||||||
}
|
}
|
||||||
if (frameHeader.getStreamId() != 0) {
|
if (frameHeader.getStreamId() != 0) {
|
||||||
throw protocolError("A stream ID must be zero.");
|
throw protocolError("A stream ID must be zero.");
|
||||||
}
|
}
|
||||||
if (frameHeader.getFlags().isAck() && frameHeader.getPayloadLength() > 0) {
|
if (frameHeader.getFlags().isAck() && frameHeader.getPayloadLength() > 0) {
|
||||||
throw protocolError("Ack settings frame must have an empty payload.");
|
throw protocolError("Ack settings frame must have an empty payload.");
|
||||||
}
|
}
|
||||||
if (frameHeader.getPayloadLength() % 5 > 0) {
|
if (frameHeader.getPayloadLength() % 5 > 0) {
|
||||||
throw protocolError("Frame length %d invalid.", frameHeader.getPayloadLength());
|
throw protocolError("Frame length %d invalid.", frameHeader.getPayloadLength());
|
||||||
}
|
}
|
||||||
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
|
||||||
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
|
||||||
ByteBufAllocator alloc) throws Http2Exception {
|
|
||||||
DefaultHttp2SettingsFrame.Builder builder = new DefaultHttp2SettingsFrame.Builder();
|
|
||||||
builder.setAck(header.getFlags().isAck());
|
|
||||||
|
|
||||||
int numSettings = header.getPayloadLength() / 5;
|
|
||||||
for (int index = 0; index < numSettings; ++index) {
|
|
||||||
short id = payload.readUnsignedByte();
|
|
||||||
long value = payload.readUnsignedInt();
|
|
||||||
switch (id) {
|
|
||||||
case SETTINGS_HEADER_TABLE_SIZE:
|
|
||||||
if (value <= 0L || value > Integer.MAX_VALUE) {
|
|
||||||
throw protocolError("Invalid header table size setting: %d", value);
|
|
||||||
}
|
|
||||||
builder.setHeaderTableSize((int) value);
|
|
||||||
break;
|
|
||||||
case SETTINGS_ENABLE_PUSH:
|
|
||||||
if (value != 0L && value != 1L) {
|
|
||||||
throw protocolError("Invalid enable push setting: %d", value);
|
|
||||||
}
|
|
||||||
builder.setPushEnabled(value == 1);
|
|
||||||
break;
|
|
||||||
case SETTINGS_MAX_CONCURRENT_STREAMS:
|
|
||||||
if (value < 0L) {
|
|
||||||
throw protocolError("Invalid max concurrent streams setting: %d", value);
|
|
||||||
}
|
|
||||||
builder.setMaxConcurrentStreams(value);
|
|
||||||
break;
|
|
||||||
case SETTINGS_INITIAL_WINDOW_SIZE:
|
|
||||||
if (value < 0L || value > Integer.MAX_VALUE) {
|
|
||||||
throw protocolError("Invalid initial window size setting: %d", value);
|
|
||||||
}
|
|
||||||
builder.setInitialWindowSize((int) value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw protocolError("Unsupported setting: %d", id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.build();
|
@Override
|
||||||
}
|
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
||||||
|
ByteBufAllocator alloc) throws Http2Exception {
|
||||||
|
DefaultHttp2SettingsFrame.Builder builder = new DefaultHttp2SettingsFrame.Builder();
|
||||||
|
builder.setAck(header.getFlags().isAck());
|
||||||
|
|
||||||
|
int numSettings = header.getPayloadLength() / 5;
|
||||||
|
for (int index = 0; index < numSettings; ++index) {
|
||||||
|
short id = payload.readUnsignedByte();
|
||||||
|
long value = payload.readUnsignedInt();
|
||||||
|
switch (id) {
|
||||||
|
case SETTINGS_HEADER_TABLE_SIZE:
|
||||||
|
if (value <= 0L || value > Integer.MAX_VALUE) {
|
||||||
|
throw protocolError("Invalid header table size setting: %d", value);
|
||||||
|
}
|
||||||
|
builder.setHeaderTableSize((int) value);
|
||||||
|
break;
|
||||||
|
case SETTINGS_ENABLE_PUSH:
|
||||||
|
if (value != 0L && value != 1L) {
|
||||||
|
throw protocolError("Invalid enable push setting: %d", value);
|
||||||
|
}
|
||||||
|
builder.setPushEnabled(value == 1);
|
||||||
|
break;
|
||||||
|
case SETTINGS_MAX_CONCURRENT_STREAMS:
|
||||||
|
if (value < 0L) {
|
||||||
|
throw protocolError("Invalid max concurrent streams setting: %d", value);
|
||||||
|
}
|
||||||
|
builder.setMaxConcurrentStreams(value);
|
||||||
|
break;
|
||||||
|
case SETTINGS_INITIAL_WINDOW_SIZE:
|
||||||
|
if (value < 0L || value > Integer.MAX_VALUE) {
|
||||||
|
throw protocolError("Invalid initial window size setting: %d", value);
|
||||||
|
}
|
||||||
|
builder.setInitialWindowSize((int) value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw protocolError("Unsupported setting: %d", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRA
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_RST_STREAM;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_RST_STREAM;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_SETTINGS;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_SETTINGS;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_WINDOW_UPDATE;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_WINDOW_UPDATE;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -35,7 +36,7 @@ import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
|
|||||||
/**
|
/**
|
||||||
* A composite {@link Http2FrameUnmarshaller} that supports all frames identified by the HTTP2 spec.
|
* A composite {@link Http2FrameUnmarshaller} that supports all frames identified by the HTTP2 spec.
|
||||||
* This unmarshalls the following frames:
|
* This unmarshalls the following frames:
|
||||||
* <p>
|
* <p/>
|
||||||
* {@link Http2DataFrame} (buffer is a slice of input buffer - must be copied if persisted)<br>
|
* {@link Http2DataFrame} (buffer is a slice of input buffer - must be copied if persisted)<br>
|
||||||
* {@link Http2GoAwayFrame} (buffer is a slice of input buffer - must be copied if persisted)<br>
|
* {@link Http2GoAwayFrame} (buffer is a slice of input buffer - must be copied if persisted)<br>
|
||||||
* {@link Http2HeadersFrame}<br>
|
* {@link Http2HeadersFrame}<br>
|
||||||
@ -48,68 +49,68 @@ import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
|
|||||||
*/
|
*/
|
||||||
public class Http2StandardFrameUnmarshaller implements Http2FrameUnmarshaller {
|
public class Http2StandardFrameUnmarshaller implements Http2FrameUnmarshaller {
|
||||||
|
|
||||||
private final Http2FrameUnmarshaller[] unmarshallers;
|
private final Http2FrameUnmarshaller[] unmarshallers;
|
||||||
private Http2FrameUnmarshaller activeUnmarshaller;
|
private Http2FrameUnmarshaller activeUnmarshaller;
|
||||||
|
|
||||||
public Http2StandardFrameUnmarshaller() {
|
public Http2StandardFrameUnmarshaller() {
|
||||||
this(new DefaultHttp2HeadersDecoder());
|
this(new DefaultHttp2HeadersDecoder());
|
||||||
}
|
|
||||||
|
|
||||||
public Http2StandardFrameUnmarshaller(Http2HeadersDecoder headersDecoder) {
|
|
||||||
unmarshallers = new Http2FrameUnmarshaller[FRAME_TYPE_CONTINUATION + 1];
|
|
||||||
unmarshallers[FRAME_TYPE_DATA] = new Http2DataFrameUnmarshaller();
|
|
||||||
unmarshallers[FRAME_TYPE_HEADERS] = new Http2HeadersFrameUnmarshaller(headersDecoder);
|
|
||||||
unmarshallers[FRAME_TYPE_PRIORITY] = new Http2PriorityFrameUnmarshaller();
|
|
||||||
unmarshallers[FRAME_TYPE_RST_STREAM] = new Http2RstStreamFrameUnmarshaller();
|
|
||||||
unmarshallers[FRAME_TYPE_SETTINGS] = new Http2SettingsFrameUnmarshaller();
|
|
||||||
unmarshallers[FRAME_TYPE_PUSH_PROMISE] = new Http2PushPromiseFrameUnmarshaller(headersDecoder);
|
|
||||||
unmarshallers[FRAME_TYPE_PING] = new Http2PingFrameUnmarshaller();
|
|
||||||
unmarshallers[FRAME_TYPE_GO_AWAY] = new Http2GoAwayFrameUnmarshaller();
|
|
||||||
unmarshallers[FRAME_TYPE_WINDOW_UPDATE] = new Http2WindowUpdateFrameUnmarshaller();
|
|
||||||
unmarshallers[FRAME_TYPE_CONTINUATION] = new Http2FrameUnmarshaller() {
|
|
||||||
private String msg = "Received continuation without headers or push_promise";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Http2FrameUnmarshaller unmarshall(Http2FrameHeader header) throws Http2Exception {
|
|
||||||
throw protocolError(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Http2Frame from(ByteBuf payload, ByteBufAllocator alloc) throws Http2Exception {
|
|
||||||
throw protocolError(msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Http2FrameUnmarshaller unmarshall(Http2FrameHeader header) throws Http2Exception {
|
|
||||||
// If we're not in the middle of unmarshalling a continued frame (e.g. headers,
|
|
||||||
// push_promise), select the appropriate marshaller for the frame type.
|
|
||||||
if (activeUnmarshaller == null) {
|
|
||||||
int type = header.getType();
|
|
||||||
if (type < 0 || type >= unmarshallers.length || unmarshallers[type] == null) {
|
|
||||||
throw protocolError("Unsupported frame type: %d", type);
|
|
||||||
}
|
|
||||||
|
|
||||||
activeUnmarshaller = unmarshallers[type];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the unmarshaller.
|
public Http2StandardFrameUnmarshaller(Http2HeadersDecoder headersDecoder) {
|
||||||
activeUnmarshaller.unmarshall(header);
|
unmarshallers = new Http2FrameUnmarshaller[FRAME_TYPE_CONTINUATION + 1];
|
||||||
return this;
|
unmarshallers[FRAME_TYPE_DATA] = new Http2DataFrameUnmarshaller();
|
||||||
}
|
unmarshallers[FRAME_TYPE_HEADERS] = new Http2HeadersFrameUnmarshaller(headersDecoder);
|
||||||
|
unmarshallers[FRAME_TYPE_PRIORITY] = new Http2PriorityFrameUnmarshaller();
|
||||||
|
unmarshallers[FRAME_TYPE_RST_STREAM] = new Http2RstStreamFrameUnmarshaller();
|
||||||
|
unmarshallers[FRAME_TYPE_SETTINGS] = new Http2SettingsFrameUnmarshaller();
|
||||||
|
unmarshallers[FRAME_TYPE_PUSH_PROMISE] = new Http2PushPromiseFrameUnmarshaller(headersDecoder);
|
||||||
|
unmarshallers[FRAME_TYPE_PING] = new Http2PingFrameUnmarshaller();
|
||||||
|
unmarshallers[FRAME_TYPE_GO_AWAY] = new Http2GoAwayFrameUnmarshaller();
|
||||||
|
unmarshallers[FRAME_TYPE_WINDOW_UPDATE] = new Http2WindowUpdateFrameUnmarshaller();
|
||||||
|
unmarshallers[FRAME_TYPE_CONTINUATION] = new Http2FrameUnmarshaller() {
|
||||||
|
private String msg = "Received continuation without headers or push_promise";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Http2Frame from(ByteBuf payload, ByteBufAllocator alloc) throws Http2Exception {
|
public Http2FrameUnmarshaller unmarshall(Http2FrameHeader header) throws Http2Exception {
|
||||||
if (activeUnmarshaller == null) {
|
throw protocolError(msg);
|
||||||
throw new IllegalStateException("Must call unmarshall() before calling from().");
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Http2Frame from(ByteBuf payload, ByteBufAllocator alloc) throws Http2Exception {
|
||||||
|
throw protocolError(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
Http2Frame frame = activeUnmarshaller.from(payload, alloc);
|
|
||||||
if (frame != null) {
|
@Override
|
||||||
// The unmarshall is complete and does not require more frames. Clear the active
|
public Http2FrameUnmarshaller unmarshall(Http2FrameHeader header) throws Http2Exception {
|
||||||
// marshaller so that we select a fresh marshaller next time.
|
// If we're not in the middle of unmarshalling a continued frame (e.g. headers,
|
||||||
activeUnmarshaller = null;
|
// push_promise), select the appropriate marshaller for the frame type.
|
||||||
|
if (activeUnmarshaller == null) {
|
||||||
|
int type = header.getType();
|
||||||
|
if (type < 0 || type >= unmarshallers.length || unmarshallers[type] == null) {
|
||||||
|
throw protocolError("Unsupported frame type: %d", type);
|
||||||
|
}
|
||||||
|
|
||||||
|
activeUnmarshaller = unmarshallers[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the unmarshaller.
|
||||||
|
activeUnmarshaller.unmarshall(header);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Http2Frame from(ByteBuf payload, ByteBufAllocator alloc) throws Http2Exception {
|
||||||
|
if (activeUnmarshaller == null) {
|
||||||
|
throw new IllegalStateException("Must call unmarshall() before calling from().");
|
||||||
|
}
|
||||||
|
Http2Frame frame = activeUnmarshaller.from(payload, alloc);
|
||||||
|
if (frame != null) {
|
||||||
|
// The unmarshall is complete and does not require more frames. Clear the active
|
||||||
|
// marshaller so that we select a fresh marshaller next time.
|
||||||
|
activeUnmarshaller = null;
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
}
|
}
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_WINDOW_UPDATE;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_WINDOW_UPDATE;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.PING_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.PING_FRAME_PAYLOAD_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -28,31 +29,31 @@ import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
|
|||||||
|
|
||||||
public class Http2WindowUpdateFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
public class Http2WindowUpdateFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
|
||||||
if (frameHeader.getType() != FRAME_TYPE_WINDOW_UPDATE) {
|
if (frameHeader.getType() != FRAME_TYPE_WINDOW_UPDATE) {
|
||||||
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
|
||||||
|
}
|
||||||
|
if (frameHeader.getStreamId() < 0) {
|
||||||
|
throw protocolError("Stream Id must be >=0: ", frameHeader.getStreamId());
|
||||||
|
}
|
||||||
|
if (frameHeader.getPayloadLength() < 4) {
|
||||||
|
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
|
if (frameHeader.getPayloadLength() > PING_FRAME_PAYLOAD_LENGTH) {
|
||||||
|
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (frameHeader.getStreamId() < 0) {
|
|
||||||
throw protocolError("Stream Id must be >=0: ", frameHeader.getStreamId());
|
|
||||||
}
|
|
||||||
if (frameHeader.getPayloadLength() < 4) {
|
|
||||||
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
if (frameHeader.getPayloadLength() > PING_FRAME_PAYLOAD_LENGTH) {
|
|
||||||
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
|
||||||
ByteBufAllocator alloc) throws Http2Exception {
|
ByteBufAllocator alloc) throws Http2Exception {
|
||||||
DefaultHttp2WindowUpdateFrame.Builder builder = new DefaultHttp2WindowUpdateFrame.Builder();
|
DefaultHttp2WindowUpdateFrame.Builder builder = new DefaultHttp2WindowUpdateFrame.Builder();
|
||||||
builder.setStreamId(header.getStreamId());
|
builder.setStreamId(header.getStreamId());
|
||||||
|
|
||||||
int windowSizeIncrement = readUnsignedInt(payload);
|
int windowSizeIncrement = readUnsignedInt(payload);
|
||||||
builder.setWindowSizeIncrement(windowSizeIncrement);
|
builder.setWindowSizeIncrement(windowSizeIncrement);
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package io.netty.handler.codec.http2.draft10.frame.encoder;
|
package io.netty.handler.codec.http2.draft10.frame.encoder;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -25,41 +26,41 @@ import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
|
|||||||
* Abstract base class for all {@link Http2FrameMarshaller}s.
|
* Abstract base class for all {@link Http2FrameMarshaller}s.
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractHttp2FrameMarshaller<T extends Http2Frame> implements
|
public abstract class AbstractHttp2FrameMarshaller<T extends Http2Frame> implements
|
||||||
Http2FrameMarshaller {
|
Http2FrameMarshaller {
|
||||||
|
|
||||||
private final Class<T> frameType;
|
private final Class<T> frameType;
|
||||||
|
|
||||||
protected AbstractHttp2FrameMarshaller(Class<T> frameType) {
|
protected AbstractHttp2FrameMarshaller(Class<T> frameType) {
|
||||||
if (frameType == null) {
|
if (frameType == null) {
|
||||||
throw new IllegalArgumentException("frameType must be non-null.");
|
throw new IllegalArgumentException("frameType must be non-null.");
|
||||||
}
|
}
|
||||||
this.frameType = frameType;
|
this.frameType = frameType;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void marshall(Http2Frame frame, ByteBuf out, ByteBufAllocator alloc)
|
|
||||||
throws Http2Exception {
|
|
||||||
if (frame == null) {
|
|
||||||
throw new IllegalArgumentException("frame must be non-null.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!frameType.isAssignableFrom(frame.getClass())) {
|
@Override
|
||||||
throw protocolError("Unsupported frame type: %s", frame.getClass().getName());
|
public final void marshall(Http2Frame frame, ByteBuf out, ByteBufAllocator alloc)
|
||||||
|
throws Http2Exception {
|
||||||
|
if (frame == null) {
|
||||||
|
throw new IllegalArgumentException("frame must be non-null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frameType.isAssignableFrom(frame.getClass())) {
|
||||||
|
throw protocolError("Unsupported frame type: %s", frame.getClass().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
T frameT = (T) frame;
|
||||||
|
doMarshall(frameT, out, alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
/**
|
||||||
T frameT = (T) frame;
|
* Marshals the frame to the output buffer.
|
||||||
doMarshall(frameT, out, alloc);
|
*
|
||||||
}
|
* @param frame the frame to be marshalled
|
||||||
|
* @param out the buffer to marshall the frame to.
|
||||||
/**
|
* @param alloc an allocator that this marshaller may use for creating intermediate buffers as
|
||||||
* Marshals the frame to the output buffer.
|
* needed.
|
||||||
*
|
*/
|
||||||
* @param frame the frame to be marshalled
|
protected abstract void doMarshall(T frame, ByteBuf out, ByteBufAllocator alloc)
|
||||||
* @param out the buffer to marshall the frame to.
|
throws Http2Exception;
|
||||||
* @param alloc an allocator that this marshaller may use for creating intermediate buffers as
|
|
||||||
* needed.
|
|
||||||
*/
|
|
||||||
protected abstract void doMarshall(T frame, ByteBuf out, ByteBufAllocator alloc)
|
|
||||||
throws Http2Exception;
|
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package io.netty.handler.codec.http2.draft10.frame.encoder;
|
package io.netty.handler.codec.http2.draft10.frame.encoder;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_HEADER_TABLE_SIZE;
|
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_HEADER_TABLE_SIZE;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufOutputStream;
|
import io.netty.buffer.ByteBufOutputStream;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Error;
|
import io.netty.handler.codec.http2.draft10.Http2Error;
|
||||||
@ -31,33 +32,33 @@ import com.google.common.base.Charsets;
|
|||||||
import com.twitter.hpack.Encoder;
|
import com.twitter.hpack.Encoder;
|
||||||
|
|
||||||
public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder {
|
public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder {
|
||||||
private static final Charset DEFAULT_CHARSET = Charsets.UTF_8;
|
private static final Charset DEFAULT_CHARSET = Charsets.UTF_8;
|
||||||
|
|
||||||
private final Encoder encoder;
|
private final Encoder encoder;
|
||||||
|
|
||||||
public DefaultHttp2HeadersEncoder() {
|
public DefaultHttp2HeadersEncoder() {
|
||||||
this.encoder = new Encoder(DEFAULT_HEADER_TABLE_SIZE);
|
this.encoder = new Encoder(DEFAULT_HEADER_TABLE_SIZE);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void encodeHeaders(Http2Headers headers, ByteBuf buffer) throws Http2Exception {
|
|
||||||
try {
|
|
||||||
OutputStream stream = new ByteBufOutputStream(buffer);
|
|
||||||
for (Entry<String, String> header : headers) {
|
|
||||||
byte[] key = header.getKey().getBytes(DEFAULT_CHARSET);
|
|
||||||
byte[] value = header.getValue().getBytes(DEFAULT_CHARSET);
|
|
||||||
encoder.encodeHeader(stream, key, value);
|
|
||||||
}
|
|
||||||
encoder.endHeaders(stream);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw Http2Exception.format(Http2Error.COMPRESSION_ERROR, "Failed encoding headers block: %s",
|
|
||||||
e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setHeaderTableSize(int size) throws Http2Exception {
|
public void encodeHeaders(Http2Headers headers, ByteBuf buffer) throws Http2Exception {
|
||||||
// TODO: can we throw away the encoder and create a new one?
|
try {
|
||||||
}
|
OutputStream stream = new ByteBufOutputStream(buffer);
|
||||||
|
for (Entry<String, String> header : headers) {
|
||||||
|
byte[] key = header.getKey().getBytes(DEFAULT_CHARSET);
|
||||||
|
byte[] value = header.getValue().getBytes(DEFAULT_CHARSET);
|
||||||
|
encoder.encodeHeader(stream, key, value);
|
||||||
|
}
|
||||||
|
encoder.endHeaders(stream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw Http2Exception.format(Http2Error.COMPRESSION_ERROR, "Failed encoding headers block: %s",
|
||||||
|
e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeaderTableSize(int size) throws Http2Exception {
|
||||||
|
// TODO: can we throw away the encoder and create a new one?
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRA
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_DATA;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_DATA;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.setPaddingFlags;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.setPaddingFlags;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.writePaddingLength;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.writePaddingLength;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
|
import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
|
||||||
@ -27,41 +28,41 @@ import io.netty.handler.codec.http2.draft10.frame.Http2Flags;
|
|||||||
|
|
||||||
public class Http2DataFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2DataFrame> {
|
public class Http2DataFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2DataFrame> {
|
||||||
|
|
||||||
public Http2DataFrameMarshaller() {
|
public Http2DataFrameMarshaller() {
|
||||||
super(Http2DataFrame.class);
|
super(Http2DataFrame.class);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doMarshall(Http2DataFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
|
||||||
ByteBuf data = frame.content();
|
|
||||||
|
|
||||||
Http2Flags flags = getFlags(frame);
|
|
||||||
|
|
||||||
// Write the frame header.
|
|
||||||
int payloadLength = data.readableBytes() + frame.getPaddingLength()
|
|
||||||
+ (flags.isPadHighPresent() ? 1 : 0) + (flags.isPadLowPresent() ? 1 : 0);
|
|
||||||
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
|
||||||
out.writeShort(payloadLength);
|
|
||||||
out.writeByte(FRAME_TYPE_DATA);
|
|
||||||
out.writeByte(flags.getValue());
|
|
||||||
out.writeInt(frame.getStreamId());
|
|
||||||
|
|
||||||
writePaddingLength(frame.getPaddingLength(), out);
|
|
||||||
|
|
||||||
// Write the data.
|
|
||||||
out.writeBytes(data, data.readerIndex(), data.readableBytes());
|
|
||||||
|
|
||||||
// Write the required padding.
|
|
||||||
out.writeZero(frame.getPaddingLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Http2Flags getFlags(Http2DataFrame frame) {
|
|
||||||
short flags = 0;
|
|
||||||
if (frame.isEndOfStream()) {
|
|
||||||
flags |= FLAG_END_STREAM;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flags = setPaddingFlags(flags, frame.getPaddingLength());
|
@Override
|
||||||
return new Http2Flags(flags);
|
protected void doMarshall(Http2DataFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
||||||
}
|
ByteBuf data = frame.content();
|
||||||
|
|
||||||
|
Http2Flags flags = getFlags(frame);
|
||||||
|
|
||||||
|
// Write the frame header.
|
||||||
|
int payloadLength = data.readableBytes() + frame.getPaddingLength()
|
||||||
|
+ (flags.isPadHighPresent() ? 1 : 0) + (flags.isPadLowPresent() ? 1 : 0);
|
||||||
|
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
|
out.writeShort(payloadLength);
|
||||||
|
out.writeByte(FRAME_TYPE_DATA);
|
||||||
|
out.writeByte(flags.getValue());
|
||||||
|
out.writeInt(frame.getStreamId());
|
||||||
|
|
||||||
|
writePaddingLength(frame.getPaddingLength(), out);
|
||||||
|
|
||||||
|
// Write the data.
|
||||||
|
out.writeBytes(data, data.readerIndex(), data.readableBytes());
|
||||||
|
|
||||||
|
// Write the required padding.
|
||||||
|
out.writeZero(frame.getPaddingLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Http2Flags getFlags(Http2DataFrame frame) {
|
||||||
|
short flags = 0;
|
||||||
|
if (frame.isEndOfStream()) {
|
||||||
|
flags |= FLAG_END_STREAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
flags = setPaddingFlags(flags, frame.getPaddingLength());
|
||||||
|
return new Http2Flags(flags);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,22 +29,22 @@ import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
|
|||||||
*/
|
*/
|
||||||
public class Http2FrameEncoder extends MessageToByteEncoder<Http2Frame> {
|
public class Http2FrameEncoder extends MessageToByteEncoder<Http2Frame> {
|
||||||
|
|
||||||
private final Http2FrameMarshaller frameMarshaller;
|
private final Http2FrameMarshaller frameMarshaller;
|
||||||
|
|
||||||
public Http2FrameEncoder() {
|
public Http2FrameEncoder() {
|
||||||
this(new Http2StandardFrameMarshaller());
|
this(new Http2StandardFrameMarshaller());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Http2FrameEncoder(Http2FrameMarshaller frameMarshaller) {
|
public Http2FrameEncoder(Http2FrameMarshaller frameMarshaller) {
|
||||||
this.frameMarshaller = frameMarshaller;
|
this.frameMarshaller = frameMarshaller;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void encode(ChannelHandlerContext ctx, Http2Frame frame, ByteBuf out) throws Exception {
|
protected void encode(ChannelHandlerContext ctx, Http2Frame frame, ByteBuf out) throws Exception {
|
||||||
try {
|
try {
|
||||||
frameMarshaller.marshall(frame, out, ctx.alloc());
|
frameMarshaller.marshall(frame, out, ctx.alloc());
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
ctx.fireExceptionCaught(t);
|
ctx.fireExceptionCaught(t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -25,14 +25,14 @@ import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
|
|||||||
*/
|
*/
|
||||||
public interface Http2FrameMarshaller {
|
public interface Http2FrameMarshaller {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marshalls the given frame to the output buffer.
|
* Marshalls the given frame to the output buffer.
|
||||||
*
|
*
|
||||||
* @param frame the frame to be marshalled.
|
* @param frame the frame to be marshalled.
|
||||||
* @param out the buffer to marshall the frame to.
|
* @param out the buffer to marshall the frame to.
|
||||||
* @param alloc an allocator that this marshaller may use for creating intermediate buffers as
|
* @param alloc an allocator that this marshaller may use for creating intermediate buffers as
|
||||||
* needed.
|
* needed.
|
||||||
* @throws Http2Exception thrown if the given fram is not supported by this marshaller.
|
* @throws Http2Exception thrown if the given fram is not supported by this marshaller.
|
||||||
*/
|
*/
|
||||||
void marshall(Http2Frame frame, ByteBuf out, ByteBufAllocator alloc) throws Http2Exception;
|
void marshall(Http2Frame frame, ByteBuf out, ByteBufAllocator alloc) throws Http2Exception;
|
||||||
}
|
}
|
||||||
|
@ -18,32 +18,33 @@ package io.netty.handler.codec.http2.draft10.frame.encoder;
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_GO_AWAY;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_GO_AWAY;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.writeUnsignedInt;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.writeUnsignedInt;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.frame.Http2GoAwayFrame;
|
import io.netty.handler.codec.http2.draft10.frame.Http2GoAwayFrame;
|
||||||
|
|
||||||
public class Http2GoAwayFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2GoAwayFrame> {
|
public class Http2GoAwayFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2GoAwayFrame> {
|
||||||
|
|
||||||
public Http2GoAwayFrameMarshaller() {
|
public Http2GoAwayFrameMarshaller() {
|
||||||
super(Http2GoAwayFrame.class);
|
super(Http2GoAwayFrame.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doMarshall(Http2GoAwayFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
protected void doMarshall(Http2GoAwayFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
||||||
ByteBuf data = frame.content();
|
ByteBuf data = frame.content();
|
||||||
|
|
||||||
// Write the frame header.
|
// Write the frame header.
|
||||||
int payloadLength = data.readableBytes() + 8;
|
int payloadLength = data.readableBytes() + 8;
|
||||||
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
out.writeShort(payloadLength);
|
out.writeShort(payloadLength);
|
||||||
out.writeByte(FRAME_TYPE_GO_AWAY);
|
out.writeByte(FRAME_TYPE_GO_AWAY);
|
||||||
out.writeByte(0);
|
out.writeByte(0);
|
||||||
out.writeInt(0);
|
out.writeInt(0);
|
||||||
|
|
||||||
out.writeInt(frame.getLastStreamId());
|
out.writeInt(frame.getLastStreamId());
|
||||||
writeUnsignedInt(frame.getErrorCode(), out);
|
writeUnsignedInt(frame.getErrorCode(), out);
|
||||||
|
|
||||||
// Write the debug data.
|
// Write the debug data.
|
||||||
out.writeBytes(data, data.readerIndex(), data.readableBytes());
|
out.writeBytes(data, data.readerIndex(), data.readableBytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,16 +24,16 @@ import io.netty.handler.codec.http2.draft10.Http2Headers;
|
|||||||
*/
|
*/
|
||||||
public interface Http2HeadersEncoder {
|
public interface Http2HeadersEncoder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes the given headers and writes the output headers block to the given output buffer.
|
* Encodes the given headers and writes the output headers block to the given output buffer.
|
||||||
*
|
*
|
||||||
* @param headers the headers to be encoded.
|
* @param headers the headers to be encoded.
|
||||||
* @param buffer the buffer to write the headers to.
|
* @param buffer the buffer to write the headers to.
|
||||||
*/
|
*/
|
||||||
void encodeHeaders(Http2Headers headers, ByteBuf buffer) throws Http2Exception;
|
void encodeHeaders(Http2Headers headers, ByteBuf buffer) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the maximum header table size for this encoder.
|
* Updates the maximum header table size for this encoder.
|
||||||
*/
|
*/
|
||||||
void setHeaderTableSize(int size) throws Http2Exception;
|
void setHeaderTableSize(int size) throws Http2Exception;
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRA
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_CONTINUATION;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_CONTINUATION;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_HEADERS;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_HEADERS;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -32,85 +33,85 @@ import com.google.common.base.Preconditions;
|
|||||||
|
|
||||||
public class Http2HeadersFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2HeadersFrame> {
|
public class Http2HeadersFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2HeadersFrame> {
|
||||||
|
|
||||||
private final Http2HeadersEncoder headersEncoder;
|
private final Http2HeadersEncoder headersEncoder;
|
||||||
|
|
||||||
public Http2HeadersFrameMarshaller(Http2HeadersEncoder headersEncoder) {
|
public Http2HeadersFrameMarshaller(Http2HeadersEncoder headersEncoder) {
|
||||||
super(Http2HeadersFrame.class);
|
super(Http2HeadersFrame.class);
|
||||||
this.headersEncoder = Preconditions.checkNotNull(headersEncoder, "headersEncoder");
|
this.headersEncoder = Preconditions.checkNotNull(headersEncoder, "headersEncoder");
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doMarshall(Http2HeadersFrame frame, ByteBuf out, ByteBufAllocator alloc)
|
|
||||||
throws Http2Exception {
|
|
||||||
// TODO(nathanmittler): include padding?
|
|
||||||
|
|
||||||
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH;
|
|
||||||
boolean hasPriority = frame.getPriority() != DEFAULT_STREAM_PRIORITY;
|
|
||||||
if (hasPriority) {
|
|
||||||
// The first frame will include the priority.
|
|
||||||
maxFragmentLength -= 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode the entire header block into an intermediate buffer.
|
@Override
|
||||||
ByteBuf headerBlock = alloc.buffer();
|
protected void doMarshall(Http2HeadersFrame frame, ByteBuf out, ByteBufAllocator alloc)
|
||||||
headersEncoder.encodeHeaders(frame.getHeaders(), headerBlock);
|
throws Http2Exception {
|
||||||
|
// TODO(nathanmittler): include padding?
|
||||||
|
|
||||||
ByteBuf fragment =
|
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
|
boolean hasPriority = frame.getPriority() != DEFAULT_STREAM_PRIORITY;
|
||||||
int payloadLength = fragment.readableBytes() + (hasPriority ? 4 : 0);
|
if (hasPriority) {
|
||||||
boolean endOfHeaders = headerBlock.readableBytes() == 0;
|
// The first frame will include the priority.
|
||||||
|
maxFragmentLength -= 4;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the flags for the frame.
|
// Encode the entire header block into an intermediate buffer.
|
||||||
short flags = 0;
|
ByteBuf headerBlock = alloc.buffer();
|
||||||
if (endOfHeaders) {
|
headersEncoder.encodeHeaders(frame.getHeaders(), headerBlock);
|
||||||
flags |= FLAG_END_HEADERS;
|
|
||||||
}
|
ByteBuf fragment =
|
||||||
if (frame.isEndOfStream()) {
|
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
|
||||||
flags |= FLAG_END_STREAM;
|
int payloadLength = fragment.readableBytes() + (hasPriority ? 4 : 0);
|
||||||
}
|
boolean endOfHeaders = headerBlock.readableBytes() == 0;
|
||||||
if (hasPriority) {
|
|
||||||
flags |= FLAG_PRIORITY;
|
// Get the flags for the frame.
|
||||||
|
short flags = 0;
|
||||||
|
if (endOfHeaders) {
|
||||||
|
flags |= FLAG_END_HEADERS;
|
||||||
|
}
|
||||||
|
if (frame.isEndOfStream()) {
|
||||||
|
flags |= FLAG_END_STREAM;
|
||||||
|
}
|
||||||
|
if (hasPriority) {
|
||||||
|
flags |= FLAG_PRIORITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the frame header.
|
||||||
|
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
|
out.writeShort(payloadLength);
|
||||||
|
out.writeByte(FRAME_TYPE_HEADERS);
|
||||||
|
out.writeByte(flags);
|
||||||
|
out.writeInt(frame.getStreamId());
|
||||||
|
|
||||||
|
// Write out the priority if it's present.
|
||||||
|
if (hasPriority) {
|
||||||
|
out.writeInt(frame.getPriority());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the first fragment.
|
||||||
|
out.writeBytes(fragment);
|
||||||
|
|
||||||
|
// Process any continuation frames there might be.
|
||||||
|
while (headerBlock.readableBytes() > 0) {
|
||||||
|
writeContinuationFrame(frame.getStreamId(), headerBlock, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the intermediate buffer.
|
||||||
|
headerBlock.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the frame header.
|
/**
|
||||||
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
* Writes a single continuation frame with a fragment of the header block to the output buffer.
|
||||||
out.writeShort(payloadLength);
|
*/
|
||||||
out.writeByte(FRAME_TYPE_HEADERS);
|
private void writeContinuationFrame(int streamId, ByteBuf headerBlock, ByteBuf out) {
|
||||||
out.writeByte(flags);
|
ByteBuf fragment =
|
||||||
out.writeInt(frame.getStreamId());
|
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), MAX_FRAME_PAYLOAD_LENGTH));
|
||||||
|
|
||||||
// Write out the priority if it's present.
|
// Write the frame header.
|
||||||
if (hasPriority) {
|
out.ensureWritable(FRAME_HEADER_LENGTH + fragment.readableBytes());
|
||||||
out.writeInt(frame.getPriority());
|
out.writeShort(fragment.readableBytes());
|
||||||
|
out.writeByte(FRAME_TYPE_CONTINUATION);
|
||||||
|
out.writeByte(headerBlock.readableBytes() == 0 ? FLAG_END_HEADERS : 0);
|
||||||
|
out.writeInt(streamId);
|
||||||
|
|
||||||
|
// Write the headers block.
|
||||||
|
out.writeBytes(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the first fragment.
|
|
||||||
out.writeBytes(fragment);
|
|
||||||
|
|
||||||
// Process any continuation frames there might be.
|
|
||||||
while (headerBlock.readableBytes() > 0) {
|
|
||||||
writeContinuationFrame(frame.getStreamId(), headerBlock, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release the intermediate buffer.
|
|
||||||
headerBlock.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a single continuation frame with a fragment of the header block to the output buffer.
|
|
||||||
*/
|
|
||||||
private void writeContinuationFrame(int streamId, ByteBuf headerBlock, ByteBuf out) {
|
|
||||||
ByteBuf fragment =
|
|
||||||
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), MAX_FRAME_PAYLOAD_LENGTH));
|
|
||||||
|
|
||||||
// Write the frame header.
|
|
||||||
out.ensureWritable(FRAME_HEADER_LENGTH + fragment.readableBytes());
|
|
||||||
out.writeShort(fragment.readableBytes());
|
|
||||||
out.writeByte(FRAME_TYPE_CONTINUATION);
|
|
||||||
out.writeByte(headerBlock.readableBytes() == 0 ? FLAG_END_HEADERS : 0);
|
|
||||||
out.writeInt(streamId);
|
|
||||||
|
|
||||||
// Write the headers block.
|
|
||||||
out.writeBytes(fragment);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,29 +18,30 @@ package io.netty.handler.codec.http2.draft10.frame.encoder;
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_ACK;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_ACK;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PING;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PING;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.frame.Http2PingFrame;
|
import io.netty.handler.codec.http2.draft10.frame.Http2PingFrame;
|
||||||
|
|
||||||
public class Http2PingFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2PingFrame> {
|
public class Http2PingFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2PingFrame> {
|
||||||
|
|
||||||
public Http2PingFrameMarshaller() {
|
public Http2PingFrameMarshaller() {
|
||||||
super(Http2PingFrame.class);
|
super(Http2PingFrame.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doMarshall(Http2PingFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
protected void doMarshall(Http2PingFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
||||||
ByteBuf data = frame.content();
|
ByteBuf data = frame.content();
|
||||||
|
|
||||||
// Write the frame header.
|
// Write the frame header.
|
||||||
int payloadLength = data.readableBytes();
|
int payloadLength = data.readableBytes();
|
||||||
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
out.writeShort(payloadLength);
|
out.writeShort(payloadLength);
|
||||||
out.writeByte(FRAME_TYPE_PING);
|
out.writeByte(FRAME_TYPE_PING);
|
||||||
out.writeByte(frame.isAck() ? FLAG_ACK : 0);
|
out.writeByte(frame.isAck() ? FLAG_ACK : 0);
|
||||||
out.writeInt(0);
|
out.writeInt(0);
|
||||||
|
|
||||||
// Write the debug data.
|
// Write the debug data.
|
||||||
out.writeBytes(data, data.readerIndex(), data.readableBytes());
|
out.writeBytes(data, data.readerIndex(), data.readableBytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,28 +17,29 @@ package io.netty.handler.codec.http2.draft10.frame.encoder;
|
|||||||
|
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PRIORITY;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PRIORITY;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.frame.Http2PriorityFrame;
|
import io.netty.handler.codec.http2.draft10.frame.Http2PriorityFrame;
|
||||||
|
|
||||||
public class Http2PriorityFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2PriorityFrame> {
|
public class Http2PriorityFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2PriorityFrame> {
|
||||||
|
|
||||||
public Http2PriorityFrameMarshaller() {
|
public Http2PriorityFrameMarshaller() {
|
||||||
super(Http2PriorityFrame.class);
|
super(Http2PriorityFrame.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doMarshall(Http2PriorityFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
protected void doMarshall(Http2PriorityFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
||||||
|
|
||||||
// Write the frame header.
|
// Write the frame header.
|
||||||
int payloadLength = 4;
|
int payloadLength = 4;
|
||||||
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
out.writeShort(payloadLength);
|
out.writeShort(payloadLength);
|
||||||
out.writeByte(FRAME_TYPE_PRIORITY);
|
out.writeByte(FRAME_TYPE_PRIORITY);
|
||||||
out.writeByte(0);
|
out.writeByte(0);
|
||||||
out.writeInt(frame.getStreamId());
|
out.writeInt(frame.getStreamId());
|
||||||
|
|
||||||
// Write out the priority if it's present.
|
// Write out the priority if it's present.
|
||||||
out.writeInt(frame.getPriority());
|
out.writeInt(frame.getPriority());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRA
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_CONTINUATION;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_CONTINUATION;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PUSH_PROMISE;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PUSH_PROMISE;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -28,66 +29,66 @@ import io.netty.handler.codec.http2.draft10.frame.Http2PushPromiseFrame;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
public class Http2PushPromiseFrameMarshaller extends
|
public class Http2PushPromiseFrameMarshaller extends
|
||||||
AbstractHttp2FrameMarshaller<Http2PushPromiseFrame> {
|
AbstractHttp2FrameMarshaller<Http2PushPromiseFrame> {
|
||||||
|
|
||||||
private final Http2HeadersEncoder headersEncoder;
|
private final Http2HeadersEncoder headersEncoder;
|
||||||
|
|
||||||
public Http2PushPromiseFrameMarshaller(Http2HeadersEncoder headersEncoder) {
|
public Http2PushPromiseFrameMarshaller(Http2HeadersEncoder headersEncoder) {
|
||||||
super(Http2PushPromiseFrame.class);
|
super(Http2PushPromiseFrame.class);
|
||||||
this.headersEncoder = Preconditions.checkNotNull(headersEncoder, "headersEncoder");
|
this.headersEncoder = Preconditions.checkNotNull(headersEncoder, "headersEncoder");
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doMarshall(Http2PushPromiseFrame frame, ByteBuf out, ByteBufAllocator alloc)
|
|
||||||
throws Http2Exception {
|
|
||||||
|
|
||||||
// Max size minus the promised stream ID.
|
|
||||||
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - 4;
|
|
||||||
|
|
||||||
// Encode the entire header block into an intermediate buffer.
|
|
||||||
ByteBuf headerBlock = alloc.buffer();
|
|
||||||
headersEncoder.encodeHeaders(frame.getHeaders(), headerBlock);
|
|
||||||
|
|
||||||
ByteBuf fragment =
|
|
||||||
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
|
|
||||||
|
|
||||||
// Write the frame header.
|
|
||||||
out.ensureWritable(FRAME_HEADER_LENGTH + fragment.readableBytes());
|
|
||||||
out.writeShort(fragment.readableBytes() + 4);
|
|
||||||
out.writeByte(FRAME_TYPE_PUSH_PROMISE);
|
|
||||||
out.writeByte(headerBlock.readableBytes() == 0 ? FLAG_END_HEADERS : 0);
|
|
||||||
out.writeInt(frame.getStreamId());
|
|
||||||
|
|
||||||
// Write out the promised stream ID.
|
|
||||||
out.writeInt(frame.getPromisedStreamId());
|
|
||||||
|
|
||||||
// Write the first fragment.
|
|
||||||
out.writeBytes(fragment);
|
|
||||||
|
|
||||||
// Process any continuation frames there might be.
|
|
||||||
while (headerBlock.readableBytes() > 0) {
|
|
||||||
writeContinuationFrame(frame.getStreamId(), headerBlock, out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release the intermediate buffer.
|
@Override
|
||||||
headerBlock.release();
|
protected void doMarshall(Http2PushPromiseFrame frame, ByteBuf out, ByteBufAllocator alloc)
|
||||||
}
|
throws Http2Exception {
|
||||||
|
|
||||||
/**
|
// Max size minus the promised stream ID.
|
||||||
* Writes a single continuation frame with a fragment of the header block to the output buffer.
|
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - 4;
|
||||||
*/
|
|
||||||
private void writeContinuationFrame(int streamId, ByteBuf headerBlock, ByteBuf out) {
|
|
||||||
ByteBuf fragment =
|
|
||||||
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), MAX_FRAME_PAYLOAD_LENGTH));
|
|
||||||
|
|
||||||
// Write the frame header.
|
// Encode the entire header block into an intermediate buffer.
|
||||||
out.ensureWritable(FRAME_HEADER_LENGTH + fragment.readableBytes());
|
ByteBuf headerBlock = alloc.buffer();
|
||||||
out.writeShort(fragment.readableBytes());
|
headersEncoder.encodeHeaders(frame.getHeaders(), headerBlock);
|
||||||
out.writeByte(FRAME_TYPE_CONTINUATION);
|
|
||||||
out.writeByte(headerBlock.readableBytes() == 0 ? FLAG_END_HEADERS : 0);
|
|
||||||
out.writeInt(streamId);
|
|
||||||
|
|
||||||
// Write the headers block.
|
ByteBuf fragment =
|
||||||
out.writeBytes(fragment);
|
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
|
||||||
}
|
|
||||||
|
// Write the frame header.
|
||||||
|
out.ensureWritable(FRAME_HEADER_LENGTH + fragment.readableBytes());
|
||||||
|
out.writeShort(fragment.readableBytes() + 4);
|
||||||
|
out.writeByte(FRAME_TYPE_PUSH_PROMISE);
|
||||||
|
out.writeByte(headerBlock.readableBytes() == 0 ? FLAG_END_HEADERS : 0);
|
||||||
|
out.writeInt(frame.getStreamId());
|
||||||
|
|
||||||
|
// Write out the promised stream ID.
|
||||||
|
out.writeInt(frame.getPromisedStreamId());
|
||||||
|
|
||||||
|
// Write the first fragment.
|
||||||
|
out.writeBytes(fragment);
|
||||||
|
|
||||||
|
// Process any continuation frames there might be.
|
||||||
|
while (headerBlock.readableBytes() > 0) {
|
||||||
|
writeContinuationFrame(frame.getStreamId(), headerBlock, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the intermediate buffer.
|
||||||
|
headerBlock.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a single continuation frame with a fragment of the header block to the output buffer.
|
||||||
|
*/
|
||||||
|
private void writeContinuationFrame(int streamId, ByteBuf headerBlock, ByteBuf out) {
|
||||||
|
ByteBuf fragment =
|
||||||
|
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), MAX_FRAME_PAYLOAD_LENGTH));
|
||||||
|
|
||||||
|
// Write the frame header.
|
||||||
|
out.ensureWritable(FRAME_HEADER_LENGTH + fragment.readableBytes());
|
||||||
|
out.writeShort(fragment.readableBytes());
|
||||||
|
out.writeByte(FRAME_TYPE_CONTINUATION);
|
||||||
|
out.writeByte(headerBlock.readableBytes() == 0 ? FLAG_END_HEADERS : 0);
|
||||||
|
out.writeInt(streamId);
|
||||||
|
|
||||||
|
// Write the headers block.
|
||||||
|
out.writeBytes(fragment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,28 +18,29 @@ package io.netty.handler.codec.http2.draft10.frame.encoder;
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_RST_STREAM;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_RST_STREAM;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.writeUnsignedInt;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.writeUnsignedInt;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.frame.Http2RstStreamFrame;
|
import io.netty.handler.codec.http2.draft10.frame.Http2RstStreamFrame;
|
||||||
|
|
||||||
public class Http2RstStreamFrameMarshaller extends
|
public class Http2RstStreamFrameMarshaller extends
|
||||||
AbstractHttp2FrameMarshaller<Http2RstStreamFrame> {
|
AbstractHttp2FrameMarshaller<Http2RstStreamFrame> {
|
||||||
|
|
||||||
public Http2RstStreamFrameMarshaller() {
|
public Http2RstStreamFrameMarshaller() {
|
||||||
super(Http2RstStreamFrame.class);
|
super(Http2RstStreamFrame.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doMarshall(Http2RstStreamFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
protected void doMarshall(Http2RstStreamFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
||||||
|
|
||||||
// Write the frame header.
|
// Write the frame header.
|
||||||
int payloadLength = 4;
|
int payloadLength = 4;
|
||||||
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
out.writeShort(payloadLength);
|
out.writeShort(payloadLength);
|
||||||
out.writeByte(FRAME_TYPE_RST_STREAM);
|
out.writeByte(FRAME_TYPE_RST_STREAM);
|
||||||
out.writeByte(0);
|
out.writeByte(0);
|
||||||
out.writeInt(frame.getStreamId());
|
out.writeInt(frame.getStreamId());
|
||||||
|
|
||||||
writeUnsignedInt(frame.getErrorCode(), out);
|
writeUnsignedInt(frame.getErrorCode(), out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SET
|
|||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_INITIAL_WINDOW_SIZE;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_INITIAL_WINDOW_SIZE;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_MAX_CONCURRENT_STREAMS;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_MAX_CONCURRENT_STREAMS;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.writeUnsignedInt;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.writeUnsignedInt;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil;
|
import io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil;
|
||||||
@ -29,41 +30,41 @@ import io.netty.handler.codec.http2.draft10.frame.Http2SettingsFrame;
|
|||||||
|
|
||||||
public class Http2SettingsFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2SettingsFrame> {
|
public class Http2SettingsFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2SettingsFrame> {
|
||||||
|
|
||||||
public Http2SettingsFrameMarshaller() {
|
public Http2SettingsFrameMarshaller() {
|
||||||
super(Http2SettingsFrame.class);
|
super(Http2SettingsFrame.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doMarshall(Http2SettingsFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
protected void doMarshall(Http2SettingsFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
||||||
int numSettings = 0;
|
int numSettings = 0;
|
||||||
numSettings += frame.getPushEnabled() != null ? 1 : 0;
|
numSettings += frame.getPushEnabled() != null ? 1 : 0;
|
||||||
numSettings += frame.getHeaderTableSize() != null ? 1 : 0;
|
numSettings += frame.getHeaderTableSize() != null ? 1 : 0;
|
||||||
numSettings += frame.getInitialWindowSize() != null ? 1 : 0;
|
numSettings += frame.getInitialWindowSize() != null ? 1 : 0;
|
||||||
numSettings += frame.getMaxConcurrentStreams() != null ? 1 : 0;
|
numSettings += frame.getMaxConcurrentStreams() != null ? 1 : 0;
|
||||||
|
|
||||||
// Write the frame header.
|
// Write the frame header.
|
||||||
int payloadLength = 5 * numSettings;
|
int payloadLength = 5 * numSettings;
|
||||||
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
out.writeShort(payloadLength);
|
out.writeShort(payloadLength);
|
||||||
out.writeByte(FRAME_TYPE_SETTINGS);
|
out.writeByte(FRAME_TYPE_SETTINGS);
|
||||||
out.writeByte(frame.isAck() ? Http2FrameCodecUtil.FLAG_ACK : 0);
|
out.writeByte(frame.isAck() ? Http2FrameCodecUtil.FLAG_ACK : 0);
|
||||||
out.writeInt(0);
|
out.writeInt(0);
|
||||||
|
|
||||||
if (frame.getPushEnabled() != null) {
|
if (frame.getPushEnabled() != null) {
|
||||||
out.writeByte(SETTINGS_ENABLE_PUSH);
|
out.writeByte(SETTINGS_ENABLE_PUSH);
|
||||||
writeUnsignedInt(frame.getPushEnabled() ? 1L : 0L, out);
|
writeUnsignedInt(frame.getPushEnabled() ? 1L : 0L, out);
|
||||||
|
}
|
||||||
|
if (frame.getHeaderTableSize() != null) {
|
||||||
|
out.writeByte(SETTINGS_HEADER_TABLE_SIZE);
|
||||||
|
writeUnsignedInt(frame.getHeaderTableSize(), out);
|
||||||
|
}
|
||||||
|
if (frame.getInitialWindowSize() != null) {
|
||||||
|
out.writeByte(SETTINGS_INITIAL_WINDOW_SIZE);
|
||||||
|
writeUnsignedInt(frame.getInitialWindowSize(), out);
|
||||||
|
}
|
||||||
|
if (frame.getMaxConcurrentStreams() != null) {
|
||||||
|
out.writeByte(SETTINGS_MAX_CONCURRENT_STREAMS);
|
||||||
|
writeUnsignedInt(frame.getMaxConcurrentStreams(), out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (frame.getHeaderTableSize() != null) {
|
|
||||||
out.writeByte(SETTINGS_HEADER_TABLE_SIZE);
|
|
||||||
writeUnsignedInt(frame.getHeaderTableSize(), out);
|
|
||||||
}
|
|
||||||
if (frame.getInitialWindowSize() != null) {
|
|
||||||
out.writeByte(SETTINGS_INITIAL_WINDOW_SIZE);
|
|
||||||
writeUnsignedInt(frame.getInitialWindowSize(), out);
|
|
||||||
}
|
|
||||||
if (frame.getMaxConcurrentStreams() != null) {
|
|
||||||
out.writeByte(SETTINGS_MAX_CONCURRENT_STREAMS);
|
|
||||||
writeUnsignedInt(frame.getMaxConcurrentStreams(), out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package io.netty.handler.codec.http2.draft10.frame.encoder;
|
package io.netty.handler.codec.http2.draft10.frame.encoder;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||||
@ -33,7 +34,7 @@ import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
|
|||||||
/**
|
/**
|
||||||
* A composite {@link Http2FrameMarshaller} that supports all frames identified by the HTTP2 spec.
|
* A composite {@link Http2FrameMarshaller} that supports all frames identified by the HTTP2 spec.
|
||||||
* This handles marshalling for the following frame types:
|
* This handles marshalling for the following frame types:
|
||||||
* <p>
|
* <p/>
|
||||||
* {@link Http2DataFrame} <br>
|
* {@link Http2DataFrame} <br>
|
||||||
* {@link Http2GoAwayFrame} <br>
|
* {@link Http2GoAwayFrame} <br>
|
||||||
* {@link Http2HeadersFrame} <br>
|
* {@link Http2HeadersFrame} <br>
|
||||||
@ -46,65 +47,65 @@ import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
|
|||||||
*/
|
*/
|
||||||
public class Http2StandardFrameMarshaller implements Http2FrameMarshaller {
|
public class Http2StandardFrameMarshaller implements Http2FrameMarshaller {
|
||||||
|
|
||||||
private final Http2FrameMarshaller dataMarshaller;
|
private final Http2FrameMarshaller dataMarshaller;
|
||||||
private final Http2FrameMarshaller headersMarshaller;
|
private final Http2FrameMarshaller headersMarshaller;
|
||||||
private final Http2FrameMarshaller goAwayMarshaller;
|
private final Http2FrameMarshaller goAwayMarshaller;
|
||||||
private final Http2FrameMarshaller pingMarshaller;
|
private final Http2FrameMarshaller pingMarshaller;
|
||||||
private final Http2FrameMarshaller priorityMarshaller;
|
private final Http2FrameMarshaller priorityMarshaller;
|
||||||
private final Http2FrameMarshaller pushPromiseMarshaller;
|
private final Http2FrameMarshaller pushPromiseMarshaller;
|
||||||
private final Http2FrameMarshaller rstStreamMarshaller;
|
private final Http2FrameMarshaller rstStreamMarshaller;
|
||||||
private final Http2FrameMarshaller settingsMarshaller;
|
private final Http2FrameMarshaller settingsMarshaller;
|
||||||
private final Http2FrameMarshaller windowUpdateMarshaller;
|
private final Http2FrameMarshaller windowUpdateMarshaller;
|
||||||
|
|
||||||
public Http2StandardFrameMarshaller() {
|
public Http2StandardFrameMarshaller() {
|
||||||
this(new DefaultHttp2HeadersEncoder());
|
this(new DefaultHttp2HeadersEncoder());
|
||||||
}
|
|
||||||
|
|
||||||
public Http2StandardFrameMarshaller(Http2HeadersEncoder headersEncoder) {
|
|
||||||
dataMarshaller = new Http2DataFrameMarshaller();
|
|
||||||
headersMarshaller = new Http2HeadersFrameMarshaller(headersEncoder);
|
|
||||||
goAwayMarshaller = new Http2GoAwayFrameMarshaller();
|
|
||||||
pingMarshaller = new Http2PingFrameMarshaller();
|
|
||||||
priorityMarshaller = new Http2PriorityFrameMarshaller();
|
|
||||||
pushPromiseMarshaller = new Http2PushPromiseFrameMarshaller(headersEncoder);
|
|
||||||
rstStreamMarshaller = new Http2RstStreamFrameMarshaller();
|
|
||||||
settingsMarshaller = new Http2SettingsFrameMarshaller();
|
|
||||||
windowUpdateMarshaller = new Http2WindowUpdateFrameMarshaller();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void marshall(Http2Frame frame, ByteBuf out, ByteBufAllocator alloc)
|
|
||||||
throws Http2Exception {
|
|
||||||
Http2FrameMarshaller marshaller = null;
|
|
||||||
|
|
||||||
if (frame == null) {
|
|
||||||
throw new IllegalArgumentException("frame must be non-null");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frame instanceof Http2DataFrame) {
|
public Http2StandardFrameMarshaller(Http2HeadersEncoder headersEncoder) {
|
||||||
marshaller = dataMarshaller;
|
dataMarshaller = new Http2DataFrameMarshaller();
|
||||||
} else if (frame instanceof Http2HeadersFrame) {
|
headersMarshaller = new Http2HeadersFrameMarshaller(headersEncoder);
|
||||||
marshaller = headersMarshaller;
|
goAwayMarshaller = new Http2GoAwayFrameMarshaller();
|
||||||
} else if (frame instanceof Http2GoAwayFrame) {
|
pingMarshaller = new Http2PingFrameMarshaller();
|
||||||
marshaller = goAwayMarshaller;
|
priorityMarshaller = new Http2PriorityFrameMarshaller();
|
||||||
} else if (frame instanceof Http2PingFrame) {
|
pushPromiseMarshaller = new Http2PushPromiseFrameMarshaller(headersEncoder);
|
||||||
marshaller = pingMarshaller;
|
rstStreamMarshaller = new Http2RstStreamFrameMarshaller();
|
||||||
} else if (frame instanceof Http2PriorityFrame) {
|
settingsMarshaller = new Http2SettingsFrameMarshaller();
|
||||||
marshaller = priorityMarshaller;
|
windowUpdateMarshaller = new Http2WindowUpdateFrameMarshaller();
|
||||||
} else if (frame instanceof Http2PushPromiseFrame) {
|
|
||||||
marshaller = pushPromiseMarshaller;
|
|
||||||
} else if (frame instanceof Http2RstStreamFrame) {
|
|
||||||
marshaller = rstStreamMarshaller;
|
|
||||||
} else if (frame instanceof Http2SettingsFrame) {
|
|
||||||
marshaller = settingsMarshaller;
|
|
||||||
} else if (frame instanceof Http2WindowUpdateFrame) {
|
|
||||||
marshaller = windowUpdateMarshaller;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (marshaller == null) {
|
@Override
|
||||||
throw protocolError("Unsupported frame type: %s", frame.getClass().getName());
|
public void marshall(Http2Frame frame, ByteBuf out, ByteBufAllocator alloc)
|
||||||
}
|
throws Http2Exception {
|
||||||
|
Http2FrameMarshaller marshaller = null;
|
||||||
|
|
||||||
marshaller.marshall(frame, out, alloc);
|
if (frame == null) {
|
||||||
}
|
throw new IllegalArgumentException("frame must be non-null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame instanceof Http2DataFrame) {
|
||||||
|
marshaller = dataMarshaller;
|
||||||
|
} else if (frame instanceof Http2HeadersFrame) {
|
||||||
|
marshaller = headersMarshaller;
|
||||||
|
} else if (frame instanceof Http2GoAwayFrame) {
|
||||||
|
marshaller = goAwayMarshaller;
|
||||||
|
} else if (frame instanceof Http2PingFrame) {
|
||||||
|
marshaller = pingMarshaller;
|
||||||
|
} else if (frame instanceof Http2PriorityFrame) {
|
||||||
|
marshaller = priorityMarshaller;
|
||||||
|
} else if (frame instanceof Http2PushPromiseFrame) {
|
||||||
|
marshaller = pushPromiseMarshaller;
|
||||||
|
} else if (frame instanceof Http2RstStreamFrame) {
|
||||||
|
marshaller = rstStreamMarshaller;
|
||||||
|
} else if (frame instanceof Http2SettingsFrame) {
|
||||||
|
marshaller = settingsMarshaller;
|
||||||
|
} else if (frame instanceof Http2WindowUpdateFrame) {
|
||||||
|
marshaller = windowUpdateMarshaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (marshaller == null) {
|
||||||
|
throw protocolError("Unsupported frame type: %s", frame.getClass().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
marshaller.marshall(frame, out, alloc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,28 +17,29 @@ package io.netty.handler.codec.http2.draft10.frame.encoder;
|
|||||||
|
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_WINDOW_UPDATE;
|
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_WINDOW_UPDATE;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
|
import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
|
||||||
|
|
||||||
public class Http2WindowUpdateFrameMarshaller extends
|
public class Http2WindowUpdateFrameMarshaller extends
|
||||||
AbstractHttp2FrameMarshaller<Http2WindowUpdateFrame> {
|
AbstractHttp2FrameMarshaller<Http2WindowUpdateFrame> {
|
||||||
|
|
||||||
public Http2WindowUpdateFrameMarshaller() {
|
public Http2WindowUpdateFrameMarshaller() {
|
||||||
super(Http2WindowUpdateFrame.class);
|
super(Http2WindowUpdateFrame.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doMarshall(Http2WindowUpdateFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
protected void doMarshall(Http2WindowUpdateFrame frame, ByteBuf out, ByteBufAllocator alloc) {
|
||||||
|
|
||||||
// Write the frame header.
|
// Write the frame header.
|
||||||
int payloadLength = 4;
|
int payloadLength = 4;
|
||||||
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
out.writeShort(payloadLength);
|
out.writeShort(payloadLength);
|
||||||
out.writeByte(FRAME_TYPE_WINDOW_UPDATE);
|
out.writeByte(FRAME_TYPE_WINDOW_UPDATE);
|
||||||
out.writeByte(0);
|
out.writeByte(0);
|
||||||
out.writeInt(frame.getStreamId());
|
out.writeInt(frame.getStreamId());
|
||||||
|
|
||||||
out.writeInt(frame.getWindowSizeIncrement());
|
out.writeInt(frame.getWindowSizeIncrement());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user