Upgrade to HTTP/2 draft 13
Motivation: Need to upgrade HTTP/2 implementation to latest draft. Modifications: Various changes to support draft 13. Result: Support for HTTP/2 draft 13.
This commit is contained in:
parent
58cc16d106
commit
67159e7119
@ -43,7 +43,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twitter</groupId>
|
<groupId>com.twitter</groupId>
|
||||||
<artifactId>hpack</artifactId>
|
<artifactId>hpack</artifactId>
|
||||||
<version>0.7.0</version>
|
<version>0.8.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
|
@ -15,6 +15,21 @@
|
|||||||
|
|
||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_STREAM_ID;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.toByteBuf;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.toHttp2Exception;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Error.NO_ERROR;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Error.STREAM_CLOSED;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Stream.State.CLOSED;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_LOCAL;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_REMOTE;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Stream.State.OPEN;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_LOCAL;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_REMOTE;
|
||||||
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;
|
||||||
@ -27,11 +42,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Error.*;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Exception.*;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Stream.State.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for a handler of HTTP/2 frames. Handles reading and writing of HTTP/2 frames
|
* Abstract base class for a handler of HTTP/2 frames. Handles reading and writing of HTTP/2 frames
|
||||||
* as well as management of connection state and flow control for both inbound and outbound data
|
* as well as management of connection state and flow control for both inbound and outbound data
|
||||||
@ -63,11 +73,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
private ChannelFutureListener closeListener;
|
private ChannelFutureListener closeListener;
|
||||||
|
|
||||||
protected AbstractHttp2ConnectionHandler(boolean server) {
|
protected AbstractHttp2ConnectionHandler(boolean server) {
|
||||||
this(server, false);
|
this(new DefaultHttp2Connection(server));
|
||||||
}
|
|
||||||
|
|
||||||
protected AbstractHttp2ConnectionHandler(boolean server, boolean allowCompression) {
|
|
||||||
this(new DefaultHttp2Connection(server, allowCompression));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AbstractHttp2ConnectionHandler(Http2Connection connection) {
|
protected AbstractHttp2ConnectionHandler(Http2Connection connection) {
|
||||||
@ -201,10 +207,9 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
*/
|
*/
|
||||||
public final Http2Settings settings() {
|
public final Http2Settings settings() {
|
||||||
Http2Settings settings = new Http2Settings();
|
Http2Settings settings = new Http2Settings();
|
||||||
settings.allowCompressedData(connection.local().allowCompressedData());
|
|
||||||
settings.initialWindowSize(inboundFlow.initialInboundWindowSize());
|
settings.initialWindowSize(inboundFlow.initialInboundWindowSize());
|
||||||
settings.maxConcurrentStreams(connection.remote().maxStreams());
|
settings.maxConcurrentStreams(connection.remote().maxStreams());
|
||||||
settings.maxHeaderTableSize(frameReader.maxHeaderTableSize());
|
settings.headerTableSize(frameReader.maxHeaderTableSize());
|
||||||
if (!connection.isServer()) {
|
if (!connection.isServer()) {
|
||||||
// Only set the pushEnabled flag if this is a client endpoint.
|
// Only set the pushEnabled flag if this is a client endpoint.
|
||||||
settings.pushEnabled(connection.local().allowPushTo());
|
settings.pushEnabled(connection.local().allowPushTo());
|
||||||
@ -217,7 +222,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception {
|
boolean endOfStream, boolean endOfSegment) throws Http2Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -227,7 +232,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
|
public final void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
|
||||||
int padding, boolean endStream, boolean endSegment) {
|
int padding, boolean endStream, boolean endSegment) throws Http2Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -312,15 +317,8 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
* Default implementation. Does nothing.
|
* Default implementation. Does nothing.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
|
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
|
||||||
ByteBuf protocolId, String host, String origin) throws Http2Exception {
|
ByteBuf payload) {
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default implementation. Does nothing.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final ChannelHandlerContext ctx() {
|
protected final ChannelHandlerContext ctx() {
|
||||||
@ -346,10 +344,6 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
throw protocolError("Sending data after connection going away.");
|
throw protocolError("Sending data after connection going away.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!connection.remote().allowCompressedData() && compressed) {
|
|
||||||
throw protocolError("compression is disallowed for remote endpoint.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Http2Stream stream = connection.requireStream(streamId);
|
Http2Stream stream = connection.requireStream(streamId);
|
||||||
stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE);
|
stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE);
|
||||||
|
|
||||||
@ -450,7 +444,8 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
throw protocolError("Sending settings after connection going away.");
|
throw protocolError("Sending settings after connection going away.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.hasPushEnabled() && connection.isServer()) {
|
Boolean pushEnabled = settings.pushEnabled();
|
||||||
|
if (pushEnabled != null && connection.isServer()) {
|
||||||
throw protocolError("Server sending SETTINGS frame with ENABLE_PUSH specified");
|
throw protocolError("Server sending SETTINGS frame with ENABLE_PUSH specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -493,15 +488,6 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise,
|
|
||||||
int streamId, long maxAge, int port, ByteBuf protocolId, String host, String origin) {
|
|
||||||
if (!connection.isServer()) {
|
|
||||||
return promise.setFailure(protocolError("Client sending ALT_SVC frame"));
|
|
||||||
}
|
|
||||||
return frameWriter.writeAltSvc(ctx, promise, streamId, maxAge, port, protocolId, host,
|
|
||||||
origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
|
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@ -724,27 +710,28 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
* Applies settings received from the remote endpoint.
|
* Applies settings received from the remote endpoint.
|
||||||
*/
|
*/
|
||||||
private void applyRemoteSettings(Http2Settings settings) throws Http2Exception {
|
private void applyRemoteSettings(Http2Settings settings) throws Http2Exception {
|
||||||
if (settings.hasPushEnabled()) {
|
Boolean pushEnabled = settings.pushEnabled();
|
||||||
|
if (pushEnabled != null) {
|
||||||
if (!connection.isServer()) {
|
if (!connection.isServer()) {
|
||||||
throw protocolError("Client received SETTINGS frame with ENABLE_PUSH specified");
|
throw protocolError("Client received SETTINGS frame with ENABLE_PUSH specified");
|
||||||
}
|
}
|
||||||
connection.remote().allowPushTo(settings.pushEnabled());
|
connection.remote().allowPushTo(pushEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.hasAllowCompressedData()) {
|
Long maxConcurrentStreams = settings.maxConcurrentStreams();
|
||||||
connection.remote().allowCompressedData(settings.allowCompressedData());
|
if (maxConcurrentStreams != null) {
|
||||||
|
int value = (int) Math.min(maxConcurrentStreams, Integer.MAX_VALUE);
|
||||||
|
connection.local().maxStreams(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.hasMaxConcurrentStreams()) {
|
Long headerTableSize = settings.headerTableSize();
|
||||||
connection.local().maxStreams(settings.maxConcurrentStreams());
|
if (headerTableSize != null) {
|
||||||
|
frameWriter.maxHeaderTableSize(headerTableSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.hasMaxHeaderTableSize()) {
|
Integer initialWindowSize = settings.initialWindowSize();
|
||||||
frameWriter.maxHeaderTableSize(settings.maxHeaderTableSize());
|
if (initialWindowSize != null) {
|
||||||
}
|
outboundFlow.initialOutboundWindowSize(initialWindowSize);
|
||||||
|
|
||||||
if (settings.hasInitialWindowSize()) {
|
|
||||||
outboundFlow.initialOutboundWindowSize(settings.initialWindowSize());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -769,23 +756,19 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
public void onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception {
|
boolean endOfStream, boolean endOfSegment) throws Http2Exception {
|
||||||
verifyPrefaceReceived();
|
verifyPrefaceReceived();
|
||||||
|
|
||||||
if (!connection.local().allowCompressedData() && compressed) {
|
|
||||||
throw protocolError("compression is disallowed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we received a data frame for a stream which is half-closed
|
// Check if we received a data frame for a stream which is half-closed
|
||||||
Http2Stream stream = connection.requireStream(streamId);
|
Http2Stream stream = connection.requireStream(streamId);
|
||||||
stream.verifyState(STREAM_CLOSED, OPEN, HALF_CLOSED_LOCAL);
|
stream.verifyState(STREAM_CLOSED, OPEN, HALF_CLOSED_LOCAL);
|
||||||
|
|
||||||
// Apply flow control.
|
// Apply flow control.
|
||||||
inboundFlow.applyInboundFlowControl(streamId, data, padding, endOfStream, endOfSegment,
|
inboundFlow.applyInboundFlowControl(streamId, data, padding, endOfStream, endOfSegment,
|
||||||
compressed, new Http2InboundFlowController.FrameWriter() {
|
new Http2InboundFlowController.FrameWriter() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeFrame(int streamId, int windowSizeIncrement) {
|
public void writeFrame(int streamId, int windowSizeIncrement)
|
||||||
|
throws Http2Exception {
|
||||||
frameWriter.writeWindowUpdate(ctx, ctx.newPromise(), streamId,
|
frameWriter.writeWindowUpdate(ctx, ctx.newPromise(), streamId,
|
||||||
windowSizeIncrement);
|
windowSizeIncrement);
|
||||||
}
|
}
|
||||||
@ -803,7 +786,7 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
}
|
}
|
||||||
|
|
||||||
AbstractHttp2ConnectionHandler.this.onDataRead(ctx, streamId, data, padding, endOfStream,
|
AbstractHttp2ConnectionHandler.this.onDataRead(ctx, streamId, data, padding, endOfStream,
|
||||||
endOfSegment, compressed);
|
endOfSegment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -941,27 +924,28 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
* Applies settings sent from the local endpoint.
|
* Applies settings sent from the local endpoint.
|
||||||
*/
|
*/
|
||||||
private void applyLocalSettings(Http2Settings settings) throws Http2Exception {
|
private void applyLocalSettings(Http2Settings settings) throws Http2Exception {
|
||||||
if (settings.hasPushEnabled()) {
|
Boolean pushEnabled = settings.pushEnabled();
|
||||||
|
if (pushEnabled != null) {
|
||||||
if (connection.isServer()) {
|
if (connection.isServer()) {
|
||||||
throw protocolError("Server sending SETTINGS frame with ENABLE_PUSH specified");
|
throw protocolError("Server sending SETTINGS frame with ENABLE_PUSH specified");
|
||||||
}
|
}
|
||||||
connection.local().allowPushTo(settings.pushEnabled());
|
connection.local().allowPushTo(pushEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.hasAllowCompressedData()) {
|
Long maxConcurrentStreams = settings.maxConcurrentStreams();
|
||||||
connection.local().allowCompressedData(settings.allowCompressedData());
|
if (maxConcurrentStreams != null) {
|
||||||
|
int value = (int) Math.min(maxConcurrentStreams, Integer.MAX_VALUE);
|
||||||
|
connection.remote().maxStreams(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.hasMaxConcurrentStreams()) {
|
Long headerTableSize = settings.headerTableSize();
|
||||||
connection.remote().maxStreams(settings.maxConcurrentStreams());
|
if (headerTableSize != null) {
|
||||||
|
frameReader.maxHeaderTableSize(headerTableSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.hasMaxHeaderTableSize()) {
|
Integer initialWindowSize = settings.initialWindowSize();
|
||||||
frameReader.maxHeaderTableSize(settings.maxHeaderTableSize());
|
if (initialWindowSize != null) {
|
||||||
}
|
inboundFlow.initialInboundWindowSize(initialWindowSize);
|
||||||
|
|
||||||
if (settings.hasInitialWindowSize()) {
|
|
||||||
inboundFlow.initialInboundWindowSize(settings.initialWindowSize());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1045,31 +1029,9 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
|
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
|
||||||
ByteBuf protocolId, String host, String origin) throws Http2Exception {
|
ByteBuf payload) {
|
||||||
if (connection.isServer()) {
|
AbstractHttp2ConnectionHandler.this.onUnknownFrame(ctx, frameType, streamId, flags, payload);
|
||||||
throw protocolError("Server received ALT_SVC frame");
|
|
||||||
}
|
|
||||||
AbstractHttp2ConnectionHandler.this.onAltSvcRead(ctx, streamId, maxAge, port,
|
|
||||||
protocolId, host, origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
|
|
||||||
verifyPrefaceReceived();
|
|
||||||
|
|
||||||
Http2Stream stream = connection.requireStream(streamId);
|
|
||||||
verifyGoAwayNotReceived();
|
|
||||||
verifyRstStreamNotReceived(stream);
|
|
||||||
if (stream.state() == CLOSED || shouldIgnoreFrame(stream)) {
|
|
||||||
// Ignored for closed streams.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the outbound flow controller.
|
|
||||||
outboundFlow.setBlocked(streamId);
|
|
||||||
|
|
||||||
AbstractHttp2ConnectionHandler.this.onBlockedRead(ctx, streamId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1160,12 +1122,11 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
|
|||||||
|
|
||||||
// Write the frame.
|
// Write the frame.
|
||||||
ChannelFuture future =
|
ChannelFuture future =
|
||||||
frameWriter.writeData(ctx, chunkPromise, streamId, data,
|
frameWriter.writeData(ctx, chunkPromise, streamId, data, padding, endStream,
|
||||||
padding, endStream, endSegment, compressed);
|
endSegment);
|
||||||
|
|
||||||
// Close the connection on write failures that leave the outbound
|
// Close the connection on write failures that leave the outbound
|
||||||
// flow
|
// flow control window in a corrupt state.
|
||||||
// control window in a corrupt state.
|
|
||||||
future.addListener(new ChannelFutureListener() {
|
future.addListener(new ChannelFutureListener() {
|
||||||
@Override
|
@Override
|
||||||
public void operationComplete(ChannelFuture future)
|
public void operationComplete(ChannelFuture future)
|
||||||
|
@ -45,39 +45,28 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
private final Http2StreamRemovalPolicy removalPolicy;
|
private final Http2StreamRemovalPolicy removalPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a connection with compression disabled and an immediate stream removal policy.
|
* Creates a connection with an immediate stream removal policy.
|
||||||
*
|
*
|
||||||
* @param server whether or not this end-point is the server-side of the HTTP/2 connection.
|
* @param server whether or not this end-point is the server-side of the HTTP/2 connection.
|
||||||
*/
|
*/
|
||||||
public DefaultHttp2Connection(boolean server) {
|
public DefaultHttp2Connection(boolean server) {
|
||||||
this(server, false);
|
this(server, immediateRemovalPolicy());
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a connection with an immediate stream removal policy.
|
|
||||||
*
|
|
||||||
* @param server whether or not this end-point is the server-side of the HTTP/2 connection.
|
|
||||||
* @param allowCompressedData if true, compressed frames are allowed from the remote end-point.
|
|
||||||
*/
|
|
||||||
public DefaultHttp2Connection(boolean server, boolean allowCompressedData) {
|
|
||||||
this(server, allowCompressedData, immediateRemovalPolicy());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new connection with the given settings.
|
* Creates a new connection with the given settings.
|
||||||
*
|
*
|
||||||
* @param server whether or not this end-point is the server-side of the HTTP/2 connection.
|
* @param server whether or not this end-point is the server-side of the HTTP/2 connection.
|
||||||
* @param allowCompressedData if true, compressed frames are allowed from the remote end-point.
|
|
||||||
* @param removalPolicy the policy to be used for removal of closed stream.
|
* @param removalPolicy the policy to be used for removal of closed stream.
|
||||||
*/
|
*/
|
||||||
public DefaultHttp2Connection(boolean server, boolean allowCompressedData,
|
public DefaultHttp2Connection(boolean server,
|
||||||
Http2StreamRemovalPolicy removalPolicy) {
|
Http2StreamRemovalPolicy removalPolicy) {
|
||||||
if (removalPolicy == null) {
|
if (removalPolicy == null) {
|
||||||
throw new NullPointerException("removalPolicy");
|
throw new NullPointerException("removalPolicy");
|
||||||
}
|
}
|
||||||
this.removalPolicy = removalPolicy;
|
this.removalPolicy = removalPolicy;
|
||||||
localEndpoint = new DefaultEndpoint(server, allowCompressedData);
|
localEndpoint = new DefaultEndpoint(server);
|
||||||
remoteEndpoint = new DefaultEndpoint(!server, false);
|
remoteEndpoint = new DefaultEndpoint(!server);
|
||||||
|
|
||||||
// Tell the removal policy how to remove a stream from this connection.
|
// Tell the removal policy how to remove a stream from this connection.
|
||||||
removalPolicy.setAction(new Action() {
|
removalPolicy.setAction(new Action() {
|
||||||
@ -600,8 +589,7 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
private int nextStreamId;
|
private int nextStreamId;
|
||||||
private int lastStreamCreated;
|
private int lastStreamCreated;
|
||||||
private int lastKnownStream = -1;
|
private int lastKnownStream = -1;
|
||||||
private boolean pushToAllowed;
|
private boolean pushToAllowed = true;
|
||||||
private boolean allowCompressedData;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum number of active streams allowed to be created by this endpoint.
|
* The maximum number of active streams allowed to be created by this endpoint.
|
||||||
@ -613,8 +601,7 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
*/
|
*/
|
||||||
private int numActiveStreams;
|
private int numActiveStreams;
|
||||||
|
|
||||||
DefaultEndpoint(boolean server, boolean allowCompressedData) {
|
DefaultEndpoint(boolean server) {
|
||||||
this.allowCompressedData = allowCompressedData;
|
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
|
||||||
// Determine the starting stream ID for this endpoint. Client-initiated streams
|
// Determine the starting stream ID for this endpoint. Client-initiated streams
|
||||||
@ -738,16 +725,6 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
this.maxStreams = maxStreams;
|
this.maxStreams = maxStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean allowCompressedData() {
|
|
||||||
return allowCompressedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void allowCompressedData(boolean allow) {
|
|
||||||
allowCompressedData = allow;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int lastStreamCreated() {
|
public int lastStreamCreated() {
|
||||||
return lastStreamCreated;
|
return lastStreamCreated;
|
||||||
|
@ -15,14 +15,28 @@
|
|||||||
|
|
||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_LENGTH_MASK;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.readUnsignedInt;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.DATA;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.GO_AWAY;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.HEADERS;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.PING;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.PRIORITY;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.PUSH_PROMISE;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.RST_STREAM;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Exception.*;
|
|
||||||
import static io.netty.util.CharsetUtil.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link Http2FrameReader} that supports all frame types defined by the HTTP/2 specification.
|
* A {@link Http2FrameReader} that supports all frame types defined by the HTTP/2 specification.
|
||||||
*/
|
*/
|
||||||
@ -37,7 +51,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
private final Http2HeadersDecoder headersDecoder;
|
private final Http2HeadersDecoder headersDecoder;
|
||||||
|
|
||||||
private State state = State.FRAME_HEADER;
|
private State state = State.FRAME_HEADER;
|
||||||
private Http2FrameType frameType;
|
private byte frameType;
|
||||||
private int streamId;
|
private int streamId;
|
||||||
private Http2Flags flags;
|
private Http2Flags flags;
|
||||||
private int payloadLength;
|
private int payloadLength;
|
||||||
@ -52,12 +66,12 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maxHeaderTableSize(int max) {
|
public void maxHeaderTableSize(long max) {
|
||||||
headersDecoder.maxHeaderTableSize(max);
|
headersDecoder.maxHeaderTableSize((int) Math.min(max, Integer.MAX_VALUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int maxHeaderTableSize() {
|
public long maxHeaderTableSize() {
|
||||||
return headersDecoder.maxHeaderTableSize();
|
return headersDecoder.maxHeaderTableSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +134,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
|
|
||||||
// Read the header and prepare the unmarshaller to read the frame.
|
// Read the header and prepare the unmarshaller to read the frame.
|
||||||
payloadLength = in.readUnsignedShort() & FRAME_LENGTH_MASK;
|
payloadLength = in.readUnsignedShort() & FRAME_LENGTH_MASK;
|
||||||
frameType = Http2FrameType.forTypeCode(in.readUnsignedByte());
|
frameType = in.readByte();
|
||||||
flags = new Http2Flags(in.readUnsignedByte());
|
flags = new Http2Flags(in.readUnsignedByte());
|
||||||
streamId = readUnsignedInt(in);
|
streamId = readUnsignedInt(in);
|
||||||
|
|
||||||
@ -155,22 +169,16 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
case CONTINUATION:
|
case CONTINUATION:
|
||||||
verifyContinuationFrame();
|
verifyContinuationFrame();
|
||||||
break;
|
break;
|
||||||
case ALT_SVC:
|
|
||||||
verifyAltSvcFrame();
|
|
||||||
break;
|
|
||||||
case BLOCKED:
|
|
||||||
verifyBlockedFrame();
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw protocolError("Unsupported frame type: %s", frameType);
|
// Unknown frame type, could be an extension.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start reading the payload for the frame.
|
// Start reading the payload for the frame.
|
||||||
state = State.FRAME_PAYLOAD;
|
state = State.FRAME_PAYLOAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void
|
private void processPayloadState(ChannelHandlerContext ctx, ByteBuf in, Http2FrameObserver observer)
|
||||||
processPayloadState(ChannelHandlerContext ctx, ByteBuf in, Http2FrameObserver observer)
|
|
||||||
throws Http2Exception {
|
throws Http2Exception {
|
||||||
if (in.readableBytes() < payloadLength) {
|
if (in.readableBytes() < payloadLength) {
|
||||||
// Wait until the entire payload has been read.
|
// Wait until the entire payload has been read.
|
||||||
@ -212,15 +220,9 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
case CONTINUATION:
|
case CONTINUATION:
|
||||||
readContinuationFrame(payload, observer);
|
readContinuationFrame(payload, observer);
|
||||||
break;
|
break;
|
||||||
case ALT_SVC:
|
|
||||||
readAltSvcFrame(ctx, payload, observer);
|
|
||||||
break;
|
|
||||||
case BLOCKED:
|
|
||||||
observer.onBlockedRead(ctx, streamId);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
// Should never happen.
|
readUnknownFrame(ctx, payload, observer);
|
||||||
throw protocolError("Unsupported frame type: %s", frameType);
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go back to reading the next frame header.
|
// Go back to reading the next frame header.
|
||||||
@ -231,10 +233,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
verifyNotProcessingHeaders();
|
verifyNotProcessingHeaders();
|
||||||
verifyPayloadLength(payloadLength);
|
verifyPayloadLength(payloadLength);
|
||||||
|
|
||||||
if (!flags.isPaddingLengthValid()) {
|
if (payloadLength < flags.getPaddingPresenceFieldLength()) {
|
||||||
throw protocolError("Pad high is set but pad low is not");
|
|
||||||
}
|
|
||||||
if (payloadLength < flags.getNumPaddingLengthBytes()) {
|
|
||||||
throw protocolError("Frame length %d too small.", payloadLength);
|
throw protocolError("Frame length %d too small.", payloadLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,18 +242,10 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
verifyNotProcessingHeaders();
|
verifyNotProcessingHeaders();
|
||||||
verifyPayloadLength(payloadLength);
|
verifyPayloadLength(payloadLength);
|
||||||
|
|
||||||
int lengthWithoutPriority = flags.getNumPaddingLengthBytes();
|
int requiredLength = flags.getPaddingPresenceFieldLength() + flags.getNumPriorityBytes();
|
||||||
if (lengthWithoutPriority < 0) {
|
if (payloadLength < requiredLength) {
|
||||||
throw protocolError("Frame length too small." + payloadLength);
|
throw protocolError("Frame length too small." + payloadLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!flags.isPaddingLengthValid()) {
|
|
||||||
throw protocolError("Pad high is set but pad low is not");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lengthWithoutPriority < flags.getNumPaddingLengthBytes()) {
|
|
||||||
throw protocolError("Frame length %d too small for padding.", payloadLength);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyPriorityFrame() throws Http2Exception {
|
private void verifyPriorityFrame() throws Http2Exception {
|
||||||
@ -291,15 +282,11 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
verifyNotProcessingHeaders();
|
verifyNotProcessingHeaders();
|
||||||
verifyPayloadLength(payloadLength);
|
verifyPayloadLength(payloadLength);
|
||||||
|
|
||||||
if (!flags.isPaddingLengthValid()) {
|
|
||||||
throw protocolError("Pad high is set but pad low is not");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subtract the length of the promised stream ID field, to determine the length of the
|
// Subtract the length of the promised stream ID field, to determine the length of the
|
||||||
// rest of the payload (header block fragment + payload).
|
// rest of the payload (header block fragment + payload).
|
||||||
int lengthWithoutPromisedId = payloadLength - INT_FIELD_LENGTH;
|
int minLength = flags.getPaddingPresenceFieldLength() + INT_FIELD_LENGTH;
|
||||||
if (lengthWithoutPromisedId < flags.getNumPaddingLengthBytes()) {
|
if (payloadLength < minLength) {
|
||||||
throw protocolError("Frame length %d too small for padding.", payloadLength);
|
throw protocolError("Frame length %d too small.", payloadLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,48 +334,25 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
+ "Expected %d, but received %d.", headersContinuation.getStreamId(), streamId);
|
+ "Expected %d, but received %d.", headersContinuation.getStreamId(), streamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!flags.isPaddingLengthValid()) {
|
if (payloadLength < flags.getPaddingPresenceFieldLength()) {
|
||||||
throw protocolError("Pad high is set but pad low is not");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (payloadLength < flags.getNumPaddingLengthBytes()) {
|
|
||||||
throw protocolError("Frame length %d too small for padding.", payloadLength);
|
throw protocolError("Frame length %d too small for padding.", payloadLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyAltSvcFrame() throws Http2Exception {
|
|
||||||
verifyNotProcessingHeaders();
|
|
||||||
verifyStreamOrConnectionId(streamId, "Stream ID");
|
|
||||||
verifyPayloadLength(payloadLength);
|
|
||||||
|
|
||||||
if (payloadLength < 8) {
|
|
||||||
throw protocolError("Frame length too small." + payloadLength);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyBlockedFrame() throws Http2Exception {
|
|
||||||
verifyNotProcessingHeaders();
|
|
||||||
verifyStreamOrConnectionId(streamId, "Stream ID");
|
|
||||||
|
|
||||||
if (payloadLength != 0) {
|
|
||||||
throw protocolError("Invalid frame length %d.", payloadLength);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readDataFrame(ChannelHandlerContext ctx, ByteBuf payload,
|
private void readDataFrame(ChannelHandlerContext ctx, ByteBuf payload,
|
||||||
Http2FrameObserver observer) throws Http2Exception {
|
Http2FrameObserver observer) throws Http2Exception {
|
||||||
int dataPadding = flags.readPaddingLength(payload);
|
short padding = readPadding(payload);
|
||||||
|
|
||||||
// Determine how much data there is to read by removing the trailing
|
// Determine how much data there is to read by removing the trailing
|
||||||
// padding.
|
// padding.
|
||||||
int dataLength = payload.readableBytes() - dataPadding;
|
int dataLength = payload.readableBytes() - padding;
|
||||||
if (dataLength < 0) {
|
if (dataLength < 0) {
|
||||||
throw protocolError("Frame payload too small for padding.");
|
throw protocolError("Frame payload too small for padding.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBuf data = payload.readSlice(dataLength);
|
ByteBuf data = payload.readSlice(dataLength);
|
||||||
observer.onDataRead(ctx, streamId, data, dataPadding, flags.endOfStream(),
|
observer.onDataRead(ctx, streamId, data, padding, flags.endOfStream(),
|
||||||
flags.endOfSegment(), flags.compressed());
|
flags.endOfSegment());
|
||||||
payload.skipBytes(payload.readableBytes());
|
payload.skipBytes(payload.readableBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,7 +360,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
Http2FrameObserver observer) throws Http2Exception {
|
Http2FrameObserver observer) throws Http2Exception {
|
||||||
final int headersStreamId = streamId;
|
final int headersStreamId = streamId;
|
||||||
final Http2Flags headersFlags = flags;
|
final Http2Flags headersFlags = flags;
|
||||||
int padding = flags.readPaddingLength(payload);
|
final int padding = readPadding(payload);
|
||||||
|
|
||||||
// The callback that is invoked is different depending on whether priority information
|
// The callback that is invoked is different depending on whether priority information
|
||||||
// is present in the headers frame.
|
// is present in the headers frame.
|
||||||
@ -415,7 +379,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding,
|
public void processFragment(boolean endOfHeaders, ByteBuf fragment,
|
||||||
Http2FrameObserver observer) throws Http2Exception {
|
Http2FrameObserver observer) throws Http2Exception {
|
||||||
builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
|
builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
|
||||||
if (endOfHeaders) {
|
if (endOfHeaders) {
|
||||||
@ -429,7 +393,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Process the initial fragment, invoking the observer's callback if end of headers.
|
// Process the initial fragment, invoking the observer's callback if end of headers.
|
||||||
headersContinuation.processFragment(flags.endOfHeaders(), fragment, padding, observer);
|
headersContinuation.processFragment(flags.endOfHeaders(), fragment, observer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -442,7 +406,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding,
|
public void processFragment(boolean endOfHeaders, ByteBuf fragment,
|
||||||
Http2FrameObserver observer) throws Http2Exception {
|
Http2FrameObserver observer) throws Http2Exception {
|
||||||
builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
|
builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
|
||||||
if (endOfHeaders) {
|
if (endOfHeaders) {
|
||||||
@ -456,7 +420,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
|
|
||||||
// Process the initial fragment, invoking the observer's callback if end of headers.
|
// Process the initial fragment, invoking the observer's callback if end of headers.
|
||||||
final ByteBuf fragment = payload.readSlice(payload.readableBytes() - padding);
|
final ByteBuf fragment = payload.readSlice(payload.readableBytes() - padding);
|
||||||
headersContinuation.processFragment(flags.endOfHeaders(), fragment, padding, observer);
|
headersContinuation.processFragment(flags.endOfHeaders(), fragment, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readPriorityFrame(ChannelHandlerContext ctx, ByteBuf payload,
|
private void readPriorityFrame(ChannelHandlerContext ctx, ByteBuf payload,
|
||||||
@ -480,45 +444,11 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
observer.onSettingsAckRead(ctx);
|
observer.onSettingsAckRead(ctx);
|
||||||
} else {
|
} else {
|
||||||
int numSettings = payloadLength / SETTING_ENTRY_LENGTH;
|
int numSettings = payloadLength / SETTING_ENTRY_LENGTH;
|
||||||
Http2Settings settings = new Http2Settings();
|
Http2Settings settings = new Http2Settings(5);
|
||||||
for (int index = 0; index < numSettings; ++index) {
|
for (int index = 0; index < numSettings; ++index) {
|
||||||
short id = payload.readUnsignedByte();
|
int id = payload.readUnsignedShort();
|
||||||
long value = payload.readUnsignedInt();
|
long value = payload.readUnsignedInt();
|
||||||
switch (id) {
|
settings.put(id, value);
|
||||||
case SETTINGS_HEADER_TABLE_SIZE:
|
|
||||||
if (value < 0 || value > Integer.MAX_VALUE) {
|
|
||||||
throw protocolError("Invalid value for HEADER_TABLE_SIZE: %d", value);
|
|
||||||
}
|
|
||||||
settings.maxHeaderTableSize((int) value);
|
|
||||||
break;
|
|
||||||
case SETTINGS_COMPRESS_DATA:
|
|
||||||
if (value != 0 && value != 1) {
|
|
||||||
throw protocolError("Invalid value for COMPRESS_DATA: %d", value);
|
|
||||||
}
|
|
||||||
settings.allowCompressedData(value == 1);
|
|
||||||
break;
|
|
||||||
case SETTINGS_ENABLE_PUSH:
|
|
||||||
if (value != 0 && value != 1) {
|
|
||||||
throw protocolError("Invalid value for ENABLE_PUSH: %d", value);
|
|
||||||
}
|
|
||||||
settings.pushEnabled(value == 1);
|
|
||||||
break;
|
|
||||||
case SETTINGS_INITIAL_WINDOW_SIZE:
|
|
||||||
if (value < 0 || value > Integer.MAX_VALUE) {
|
|
||||||
throw protocolError("Invalid value for INITIAL_WINDOW_SIZE: %d", value);
|
|
||||||
}
|
|
||||||
settings.initialWindowSize((int) value);
|
|
||||||
break;
|
|
||||||
case SETTINGS_MAX_CONCURRENT_STREAMS:
|
|
||||||
if (value < 0 || value > Integer.MAX_VALUE) {
|
|
||||||
throw protocolError("Invalid value for MAX_CONCURRENT_STREAMS: %d",
|
|
||||||
value);
|
|
||||||
}
|
|
||||||
settings.maxConcurrentStreams((int) value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw protocolError("Unsupport setting: %d", id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
observer.onSettingsRead(ctx, settings);
|
observer.onSettingsRead(ctx, settings);
|
||||||
}
|
}
|
||||||
@ -527,7 +457,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
private void readPushPromiseFrame(final ChannelHandlerContext ctx, ByteBuf payload,
|
private void readPushPromiseFrame(final ChannelHandlerContext ctx, ByteBuf payload,
|
||||||
Http2FrameObserver observer) throws Http2Exception {
|
Http2FrameObserver observer) throws Http2Exception {
|
||||||
final int pushPromiseStreamId = streamId;
|
final int pushPromiseStreamId = streamId;
|
||||||
int padding = flags.readPaddingLength(payload);
|
final int padding = readPadding(payload);
|
||||||
final int promisedStreamId = readUnsignedInt(payload);
|
final int promisedStreamId = readUnsignedInt(payload);
|
||||||
|
|
||||||
// Create a handler that invokes the observer when the header block is complete.
|
// Create a handler that invokes the observer when the header block is complete.
|
||||||
@ -538,7 +468,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding,
|
public void processFragment(boolean endOfHeaders, ByteBuf fragment,
|
||||||
Http2FrameObserver observer) throws Http2Exception {
|
Http2FrameObserver observer) throws Http2Exception {
|
||||||
builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
|
builder().addFragment(fragment, ctx.alloc(), endOfHeaders);
|
||||||
if (endOfHeaders) {
|
if (endOfHeaders) {
|
||||||
@ -552,7 +482,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
|
|
||||||
// Process the initial fragment, invoking the observer's callback if end of headers.
|
// Process the initial fragment, invoking the observer's callback if end of headers.
|
||||||
final ByteBuf fragment = payload.readSlice(payload.readableBytes() - padding);
|
final ByteBuf fragment = payload.readSlice(payload.readableBytes() - padding);
|
||||||
headersContinuation.processFragment(flags.endOfHeaders(), fragment, padding, observer);
|
headersContinuation.processFragment(flags.endOfHeaders(), fragment, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readPingFrame(ChannelHandlerContext ctx, ByteBuf payload,
|
private void readPingFrame(ChannelHandlerContext ctx, ByteBuf payload,
|
||||||
@ -581,30 +511,26 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
|
|
||||||
private void readContinuationFrame(ByteBuf payload, Http2FrameObserver observer)
|
private void readContinuationFrame(ByteBuf payload, Http2FrameObserver observer)
|
||||||
throws Http2Exception {
|
throws Http2Exception {
|
||||||
int padding = flags.readPaddingLength(payload);
|
|
||||||
|
|
||||||
// Process the initial fragment, invoking the observer's callback if end of headers.
|
// Process the initial fragment, invoking the observer's callback if end of headers.
|
||||||
final ByteBuf continuationFragment = payload.readSlice(payload.readableBytes() - padding);
|
final ByteBuf continuationFragment = payload.readSlice(payload.readableBytes());
|
||||||
headersContinuation.processFragment(flags.endOfHeaders(), continuationFragment, padding,
|
headersContinuation.processFragment(flags.endOfHeaders(), continuationFragment,
|
||||||
observer);
|
observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readAltSvcFrame(ChannelHandlerContext ctx, ByteBuf payload,
|
private void readUnknownFrame(ChannelHandlerContext ctx, ByteBuf payload, Http2FrameObserver observer)
|
||||||
Http2FrameObserver observer) throws Http2Exception {
|
throws Http2Exception {
|
||||||
long maxAge = payload.readUnsignedInt();
|
payload = payload.readSlice(payload.readableBytes());
|
||||||
int port = payload.readUnsignedShort();
|
observer.onUnknownFrame(ctx, frameType, streamId, flags, payload);
|
||||||
payload.skipBytes(1);
|
|
||||||
short protocolIdLength = payload.readUnsignedByte();
|
|
||||||
ByteBuf protocolId = payload.readSlice(protocolIdLength);
|
|
||||||
short hostLength = payload.readUnsignedByte();
|
|
||||||
String host = payload.toString(payload.readerIndex(), hostLength, UTF_8);
|
|
||||||
payload.skipBytes(hostLength);
|
|
||||||
String origin = null;
|
|
||||||
if (payload.isReadable()) {
|
|
||||||
origin = payload.toString(UTF_8);
|
|
||||||
payload.skipBytes(payload.readableBytes());
|
|
||||||
}
|
}
|
||||||
observer.onAltSvcRead(ctx, streamId, maxAge, port, protocolId, host, origin);
|
|
||||||
|
/**
|
||||||
|
* If padding is present in the payload, reads the next byte as padding. Otherwise, returns zero.
|
||||||
|
*/
|
||||||
|
private short readPadding(ByteBuf payload) {
|
||||||
|
if (!flags.paddingPresent()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return payload.readUnsignedByte();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -625,11 +551,9 @@ public class DefaultHttp2FrameReader implements Http2FrameReader {
|
|||||||
*
|
*
|
||||||
* @param endOfHeaders whether the fragment is the last in the header block.
|
* @param endOfHeaders whether the fragment is the last in the header block.
|
||||||
* @param fragment the fragment of the header block to be added.
|
* @param fragment the fragment of the header block to be added.
|
||||||
* @param padding the amount of padding to be supplied to the {@link Http2FrameObserver}
|
|
||||||
* callback.
|
|
||||||
* @param observer the observer to be notified if the header block is completed.
|
* @param observer the observer to be notified if the header block is completed.
|
||||||
*/
|
*/
|
||||||
abstract void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding,
|
abstract void processFragment(boolean endOfHeaders, ByteBuf fragment,
|
||||||
Http2FrameObserver observer) throws Http2Exception;
|
Http2FrameObserver observer) throws Http2Exception;
|
||||||
|
|
||||||
final HeadersBuilder builder() {
|
final HeadersBuilder builder() {
|
||||||
|
@ -15,14 +15,34 @@
|
|||||||
|
|
||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_BYTE;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_WEIGHT;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.writeFrameHeader;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedInt;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedShort;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.DATA;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.GO_AWAY;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.HEADERS;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.PING;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.PRIORITY;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.PUSH_PROMISE;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.RST_STREAM;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.CompositeByteBuf;
|
import io.netty.buffer.CompositeByteBuf;
|
||||||
import io.netty.channel.ChannelFuture;
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.ChannelPromise;
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.util.collection.IntObjectMap;
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
|
||||||
import static io.netty.util.CharsetUtil.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link Http2FrameWriter} that supports all frame types defined by the HTTP/2 specification.
|
* A {@link Http2FrameWriter} that supports all frame types defined by the HTTP/2 specification.
|
||||||
@ -40,12 +60,12 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maxHeaderTableSize(int max) throws Http2Exception {
|
public void maxHeaderTableSize(long max) throws Http2Exception {
|
||||||
headersEncoder.maxHeaderTableSize(max);
|
headersEncoder.maxHeaderTableSize((int) Math.min(max, Integer.MAX_VALUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int maxHeaderTableSize() {
|
public long maxHeaderTableSize() {
|
||||||
return headersEncoder.maxHeaderTableSize();
|
return headersEncoder.maxHeaderTableSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,21 +76,21 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
|
public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
|
||||||
ByteBuf data, int padding, boolean endStream, boolean endSegment, boolean compressed) {
|
ByteBuf data, int padding, boolean endStream, boolean endSegment) {
|
||||||
try {
|
try {
|
||||||
verifyStreamId(streamId, "Stream ID");
|
verifyStreamId(streamId, "Stream ID");
|
||||||
verifyPadding(padding);
|
verifyPadding(padding);
|
||||||
|
|
||||||
Http2Flags flags =
|
Http2Flags flags =
|
||||||
Http2Flags.newBuilder().setPaddingFlags(padding).endOfStream(endStream)
|
new Http2Flags().paddingPresent(padding > 0).endOfStream(endStream)
|
||||||
.endOfSegment(endSegment).compressed(compressed).build();
|
.endOfSegment(endSegment);
|
||||||
|
|
||||||
int payloadLength = data.readableBytes() + padding + flags.getNumPaddingLengthBytes();
|
int payloadLength = data.readableBytes() + padding + flags.getPaddingPresenceFieldLength();
|
||||||
verifyPayloadLength(payloadLength);
|
verifyPayloadLength(payloadLength);
|
||||||
|
|
||||||
ByteBuf out = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
ByteBuf out = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
|
|
||||||
writeFrameHeader(out, payloadLength, Http2FrameType.DATA, flags, streamId);
|
writeFrameHeader(out, payloadLength, DATA, flags, streamId);
|
||||||
|
|
||||||
writePaddingLength(padding, out);
|
writePaddingLength(padding, out);
|
||||||
|
|
||||||
@ -111,9 +131,9 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
verifyWeight(weight);
|
verifyWeight(weight);
|
||||||
|
|
||||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + PRIORITY_ENTRY_LENGTH);
|
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + PRIORITY_ENTRY_LENGTH);
|
||||||
writeFrameHeader(frame, PRIORITY_ENTRY_LENGTH, Http2FrameType.PRIORITY,
|
writeFrameHeader(frame, PRIORITY_ENTRY_LENGTH, PRIORITY,
|
||||||
Http2Flags.EMPTY, streamId);
|
new Http2Flags(), streamId);
|
||||||
long word1 = exclusive ? 0x80000000L | streamDependency : streamDependency;
|
long word1 = exclusive ? (0x80000000L | streamDependency) : streamDependency;
|
||||||
writeUnsignedInt(word1, frame);
|
writeUnsignedInt(word1, frame);
|
||||||
|
|
||||||
// Adjust the weight so that it fits into a single byte on the wire.
|
// Adjust the weight so that it fits into a single byte on the wire.
|
||||||
@ -132,7 +152,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
verifyErrorCode(errorCode);
|
verifyErrorCode(errorCode);
|
||||||
|
|
||||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + INT_FIELD_LENGTH);
|
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + INT_FIELD_LENGTH);
|
||||||
writeFrameHeader(frame, INT_FIELD_LENGTH, Http2FrameType.RST_STREAM, Http2Flags.EMPTY,
|
writeFrameHeader(frame, INT_FIELD_LENGTH, RST_STREAM, new Http2Flags(),
|
||||||
streamId);
|
streamId);
|
||||||
writeUnsignedInt(errorCode, frame);
|
writeUnsignedInt(errorCode, frame);
|
||||||
return ctx.writeAndFlush(frame, promise);
|
return ctx.writeAndFlush(frame, promise);
|
||||||
@ -145,10 +165,16 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
public ChannelFuture writeSettings(ChannelHandlerContext ctx, ChannelPromise promise,
|
public ChannelFuture writeSettings(ChannelHandlerContext ctx, ChannelPromise promise,
|
||||||
Http2Settings settings) {
|
Http2Settings settings) {
|
||||||
try {
|
try {
|
||||||
int payloadLength = calcSettingsPayloadLength(settings);
|
if (settings == null) {
|
||||||
|
throw new NullPointerException("settings");
|
||||||
|
}
|
||||||
|
int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
|
||||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
writeFrameHeader(frame, payloadLength, Http2FrameType.SETTINGS, Http2Flags.EMPTY, 0);
|
writeFrameHeader(frame, payloadLength, SETTINGS, new Http2Flags(), 0);
|
||||||
writeSettingsPayload(settings, frame);
|
for (IntObjectMap.Entry<Long> entry : settings.entries()) {
|
||||||
|
writeUnsignedShort(entry.key(), frame);
|
||||||
|
writeUnsignedInt(entry.value(), frame);
|
||||||
|
}
|
||||||
return ctx.writeAndFlush(frame, promise);
|
return ctx.writeAndFlush(frame, promise);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
return promise.setFailure(e);
|
return promise.setFailure(e);
|
||||||
@ -159,7 +185,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) {
|
public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) {
|
||||||
try {
|
try {
|
||||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
|
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
|
||||||
writeFrameHeader(frame, 0, Http2FrameType.SETTINGS, Http2Flags.ACK_ONLY, 0);
|
writeFrameHeader(frame, 0, SETTINGS, new Http2Flags().ack(true), 0);
|
||||||
return ctx.writeAndFlush(frame, promise);
|
return ctx.writeAndFlush(frame, promise);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
return promise.setFailure(e);
|
return promise.setFailure(e);
|
||||||
@ -171,8 +197,8 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
ByteBuf data) {
|
ByteBuf data) {
|
||||||
try {
|
try {
|
||||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + data.readableBytes());
|
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + data.readableBytes());
|
||||||
Http2Flags flags = ack ? Http2Flags.ACK_ONLY : Http2Flags.EMPTY;
|
Http2Flags flags = ack ? new Http2Flags().ack(true) : new Http2Flags();
|
||||||
writeFrameHeader(frame, data.readableBytes(), Http2FrameType.PING, flags, 0);
|
writeFrameHeader(frame, data.readableBytes(), PING, flags, 0);
|
||||||
|
|
||||||
// Write the debug data.
|
// Write the debug data.
|
||||||
frame.writeBytes(data, data.readerIndex(), data.readableBytes());
|
frame.writeBytes(data, data.readerIndex(), data.readableBytes());
|
||||||
@ -198,21 +224,18 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
headersEncoder.encodeHeaders(headers, headerBlock);
|
headersEncoder.encodeHeaders(headers, headerBlock);
|
||||||
|
|
||||||
// Read the first fragment (possibly everything).
|
// Read the first fragment (possibly everything).
|
||||||
Http2Flags.Builder flags = Http2Flags.newBuilder().setPaddingFlags(padding);
|
Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
|
||||||
int promisedStreamIdLength = INT_FIELD_LENGTH;
|
int promisedStreamIdLength = INT_FIELD_LENGTH;
|
||||||
int maxFragmentLength =
|
int nonFragmentLength = promisedStreamIdLength + padding + flags.getPaddingPresenceFieldLength();
|
||||||
MAX_FRAME_PAYLOAD_LENGTH
|
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentLength;
|
||||||
- (promisedStreamIdLength + padding + flags.getNumPaddingLengthBytes());
|
|
||||||
ByteBuf fragment =
|
ByteBuf fragment =
|
||||||
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
|
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
|
||||||
|
|
||||||
flags = flags.endOfHeaders(headerBlock.readableBytes() == 0);
|
flags.endOfHeaders(headerBlock.readableBytes() == 0);
|
||||||
|
|
||||||
int payloadLength =
|
int payloadLength = fragment.readableBytes() + nonFragmentLength;
|
||||||
fragment.readableBytes() + promisedStreamIdLength + padding
|
|
||||||
+ flags.getNumPaddingLengthBytes();
|
|
||||||
ByteBuf firstFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
ByteBuf firstFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
writeFrameHeader(firstFrame, payloadLength, Http2FrameType.PUSH_PROMISE, flags.build(),
|
writeFrameHeader(firstFrame, payloadLength, PUSH_PROMISE, flags,
|
||||||
streamId);
|
streamId);
|
||||||
|
|
||||||
writePaddingLength(padding, firstFrame);
|
writePaddingLength(padding, firstFrame);
|
||||||
@ -250,7 +273,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
|
|
||||||
int payloadLength = 8 + debugData.readableBytes();
|
int payloadLength = 8 + debugData.readableBytes();
|
||||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
writeFrameHeader(frame, payloadLength, Http2FrameType.GO_AWAY, Http2Flags.EMPTY, 0);
|
writeFrameHeader(frame, payloadLength, GO_AWAY, new Http2Flags(), 0);
|
||||||
frame.writeInt(lastStreamId);
|
frame.writeInt(lastStreamId);
|
||||||
writeUnsignedInt(errorCode, frame);
|
writeUnsignedInt(errorCode, frame);
|
||||||
frame.writeBytes(debugData, debugData.readerIndex(), debugData.readableBytes());
|
frame.writeBytes(debugData, debugData.readerIndex(), debugData.readableBytes());
|
||||||
@ -270,8 +293,8 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
verifyWindowSizeIncrement(windowSizeIncrement);
|
verifyWindowSizeIncrement(windowSizeIncrement);
|
||||||
|
|
||||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + INT_FIELD_LENGTH);
|
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + INT_FIELD_LENGTH);
|
||||||
writeFrameHeader(frame, INT_FIELD_LENGTH, Http2FrameType.WINDOW_UPDATE,
|
writeFrameHeader(frame, INT_FIELD_LENGTH, WINDOW_UPDATE,
|
||||||
Http2Flags.EMPTY, streamId);
|
new Http2Flags(), streamId);
|
||||||
frame.writeInt(windowSizeIncrement);
|
frame.writeInt(windowSizeIncrement);
|
||||||
return ctx.writeAndFlush(frame, promise);
|
return ctx.writeAndFlush(frame, promise);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
@ -280,49 +303,13 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise,
|
public ChannelFuture writeFrame(ChannelHandlerContext ctx, ChannelPromise promise,
|
||||||
int streamId, long maxAge, int port, ByteBuf protocolId, String host, String origin) {
|
byte frameType, int streamId, Http2Flags flags, ByteBuf payload) {
|
||||||
try {
|
try {
|
||||||
verifyStreamOrConnectionId(streamId, "Stream ID");
|
verifyStreamOrConnectionId(streamId, "Stream ID");
|
||||||
verifyMaxAge(maxAge);
|
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payload.readableBytes());
|
||||||
verifyPort(port);
|
writeFrameHeader(frame, payload.readableBytes(), frameType, flags, streamId);
|
||||||
|
frame.writeBytes(payload);
|
||||||
// 9 bytes is the total of all fields except for the protocol ID and host.
|
|
||||||
// Breakdown: Max-Age(4) + Port(2) + reserved(1) + Proto-Len(1) + Host-Len(1) = 9
|
|
||||||
int payloadLength = 9 + protocolId.readableBytes() + host.length();
|
|
||||||
if (origin != null) {
|
|
||||||
payloadLength += origin.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
|
||||||
writeFrameHeader(frame, payloadLength, Http2FrameType.ALT_SVC, Http2Flags.EMPTY,
|
|
||||||
streamId);
|
|
||||||
writeUnsignedInt(maxAge, frame);
|
|
||||||
writeUnsignedShort(port, frame);
|
|
||||||
frame.writeZero(1);
|
|
||||||
frame.writeByte(protocolId.readableBytes());
|
|
||||||
frame.writeBytes(protocolId);
|
|
||||||
frame.writeByte(host.length());
|
|
||||||
frame.writeBytes(host.getBytes(UTF_8));
|
|
||||||
if (origin != null) {
|
|
||||||
frame.writeBytes(origin.getBytes(UTF_8));
|
|
||||||
}
|
|
||||||
return ctx.writeAndFlush(frame, promise);
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
return promise.setFailure(e);
|
|
||||||
} finally {
|
|
||||||
protocolId.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture writeBlocked(ChannelHandlerContext ctx, ChannelPromise promise,
|
|
||||||
int streamId) {
|
|
||||||
try {
|
|
||||||
verifyStreamOrConnectionId(streamId, "Stream ID");
|
|
||||||
|
|
||||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
|
|
||||||
writeFrameHeader(frame, 0, Http2FrameType.BLOCKED, Http2Flags.EMPTY, streamId);
|
|
||||||
return ctx.writeAndFlush(frame, promise);
|
return ctx.writeAndFlush(frame, promise);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
return promise.setFailure(e);
|
return promise.setFailure(e);
|
||||||
@ -345,23 +332,23 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
headerBlock = ctx.alloc().buffer();
|
headerBlock = ctx.alloc().buffer();
|
||||||
headersEncoder.encodeHeaders(headers, headerBlock);
|
headersEncoder.encodeHeaders(headers, headerBlock);
|
||||||
|
|
||||||
Http2Flags.Builder flags =
|
Http2Flags flags =
|
||||||
Http2Flags.newBuilder().endOfStream(endStream).endOfSegment(endSegment)
|
new Http2Flags().endOfStream(endStream).endOfSegment(endSegment)
|
||||||
.priorityPresent(hasPriority).setPaddingFlags(padding);
|
.priorityPresent(hasPriority).paddingPresent(padding > 0);
|
||||||
|
|
||||||
// Read the first fragment (possibly everything).
|
// Read the first fragment (possibly everything).
|
||||||
int nonFragmentBytes =
|
int nonFragmentBytes =
|
||||||
padding + flags.getNumPriorityBytes() + flags.getNumPaddingLengthBytes();
|
padding + flags.getNumPriorityBytes() + flags.getPaddingPresenceFieldLength();
|
||||||
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentBytes;
|
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentBytes;
|
||||||
ByteBuf fragment =
|
ByteBuf fragment =
|
||||||
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
|
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
|
||||||
|
|
||||||
// Set the end of headers flag for the first frame.
|
// Set the end of headers flag for the first frame.
|
||||||
flags = flags.endOfHeaders(headerBlock.readableBytes() == 0);
|
flags.endOfHeaders(headerBlock.readableBytes() == 0);
|
||||||
|
|
||||||
int payloadLength = fragment.readableBytes() + nonFragmentBytes;
|
int payloadLength = fragment.readableBytes() + nonFragmentBytes;
|
||||||
ByteBuf firstFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
ByteBuf firstFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
writeFrameHeader(firstFrame, payloadLength, Http2FrameType.HEADERS, flags.build(),
|
writeFrameHeader(firstFrame, payloadLength, HEADERS, flags,
|
||||||
streamId);
|
streamId);
|
||||||
|
|
||||||
// Write the padding length.
|
// Write the padding length.
|
||||||
@ -425,17 +412,17 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
*/
|
*/
|
||||||
private static ByteBuf createContinuationFrame(ChannelHandlerContext ctx, int streamId,
|
private static ByteBuf createContinuationFrame(ChannelHandlerContext ctx, int streamId,
|
||||||
ByteBuf headerBlock, int padding) {
|
ByteBuf headerBlock, int padding) {
|
||||||
Http2Flags.Builder flags = Http2Flags.newBuilder().setPaddingFlags(padding);
|
Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
|
||||||
int maxFragmentLength =
|
int nonFragmentLength = padding + flags.getPaddingPresenceFieldLength();
|
||||||
MAX_FRAME_PAYLOAD_LENGTH - (padding + flags.getNumPaddingLengthBytes());
|
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - nonFragmentLength;
|
||||||
ByteBuf fragment =
|
ByteBuf fragment =
|
||||||
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
|
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
|
||||||
|
|
||||||
int payloadLength = fragment.readableBytes() + padding + flags.getNumPaddingLengthBytes();
|
int payloadLength = fragment.readableBytes() + nonFragmentLength;
|
||||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
flags = flags.endOfHeaders(headerBlock.readableBytes() == 0);
|
flags = flags.endOfHeaders(headerBlock.readableBytes() == 0);
|
||||||
|
|
||||||
writeFrameHeader(frame, payloadLength, Http2FrameType.CONTINUATION, flags.build(), streamId);
|
writeFrameHeader(frame, payloadLength, CONTINUATION, flags, streamId);
|
||||||
|
|
||||||
writePaddingLength(padding, frame);
|
writePaddingLength(padding, frame);
|
||||||
|
|
||||||
@ -474,7 +461,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void verifyPadding(int padding) {
|
private static void verifyPadding(int padding) {
|
||||||
if (padding < 0 || padding > MAX_UNSIGNED_SHORT) {
|
if (padding < 0 || padding > MAX_UNSIGNED_BYTE) {
|
||||||
throw new IllegalArgumentException("Invalid padding value: " + padding);
|
throw new IllegalArgumentException("Invalid padding value: " + padding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -503,16 +490,4 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter {
|
|||||||
throw new IllegalArgumentException("WindowSizeIncrement must be >= 0");
|
throw new IllegalArgumentException("WindowSizeIncrement must be >= 0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void verifyMaxAge(long maxAge) {
|
|
||||||
if (maxAge < 0 || maxAge > MAX_UNSIGNED_INT) {
|
|
||||||
throw new IllegalArgumentException("Invalid Max Age: " + maxAge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void verifyPort(int port) {
|
|
||||||
if (port < 0 || port > MAX_UNSIGNED_SHORT) {
|
|
||||||
throw new IllegalArgumentException("Invalid port: " + port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
|
import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE;
|
||||||
import static io.netty.handler.codec.http2.Http2Exception.flowControlError;
|
import static io.netty.handler.codec.http2.Http2Exception.flowControlError;
|
||||||
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
|
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
@ -27,7 +27,7 @@ import io.netty.buffer.ByteBuf;
|
|||||||
public class DefaultHttp2InboundFlowController implements Http2InboundFlowController {
|
public class DefaultHttp2InboundFlowController implements Http2InboundFlowController {
|
||||||
|
|
||||||
private final Http2Connection connection;
|
private final Http2Connection connection;
|
||||||
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
private int initialWindowSize = DEFAULT_WINDOW_SIZE;
|
||||||
|
|
||||||
public DefaultHttp2InboundFlowController(Http2Connection connection) {
|
public DefaultHttp2InboundFlowController(Http2Connection connection) {
|
||||||
if (connection == null) {
|
if (connection == null) {
|
||||||
@ -66,7 +66,7 @@ public class DefaultHttp2InboundFlowController implements Http2InboundFlowContro
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void applyInboundFlowControl(int streamId, ByteBuf data, int padding,
|
public void applyInboundFlowControl(int streamId, ByteBuf data, int padding,
|
||||||
boolean endOfStream, boolean endOfSegment, boolean compressed, FrameWriter frameWriter)
|
boolean endOfStream, boolean endOfSegment, FrameWriter frameWriter)
|
||||||
throws Http2Exception {
|
throws Http2Exception {
|
||||||
int dataLength = data.readableBytes();
|
int dataLength = data.readableBytes();
|
||||||
applyConnectionFlowControl(dataLength, frameWriter);
|
applyConnectionFlowControl(dataLength, frameWriter);
|
||||||
|
@ -57,7 +57,7 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
|
|||||||
};
|
};
|
||||||
|
|
||||||
private final Http2Connection connection;
|
private final Http2Connection connection;
|
||||||
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
private int initialWindowSize = DEFAULT_WINDOW_SIZE;
|
||||||
|
|
||||||
public DefaultHttp2OutboundFlowController(Http2Connection connection) {
|
public DefaultHttp2OutboundFlowController(Http2Connection connection) {
|
||||||
if (connection == null) {
|
if (connection == null) {
|
||||||
@ -147,11 +147,6 @@ public class DefaultHttp2OutboundFlowController implements Http2OutboundFlowCont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBlocked(int streamId) throws Http2Exception {
|
|
||||||
// Ignore blocked frames. Just rely on window updates.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendFlowControlled(int streamId, ByteBuf data, int padding, boolean endStream,
|
public void sendFlowControlled(int streamId, ByteBuf data, int padding, boolean endStream,
|
||||||
boolean endSegment, boolean compressed, FrameWriter frameWriter) throws Http2Exception {
|
boolean endSegment, boolean compressed, FrameWriter frameWriter) throws Http2Exception {
|
||||||
|
@ -38,12 +38,6 @@ public class DelegatingHttp2ConnectionHandler extends AbstractHttp2ConnectionHan
|
|||||||
this.observer = observer;
|
this.observer = observer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DelegatingHttp2ConnectionHandler(boolean server, boolean allowCompression,
|
|
||||||
Http2FrameObserver observer) {
|
|
||||||
super(server, allowCompression);
|
|
||||||
this.observer = observer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DelegatingHttp2ConnectionHandler(Http2Connection connection,
|
public DelegatingHttp2ConnectionHandler(Http2Connection connection,
|
||||||
Http2FrameReader frameReader, Http2FrameWriter frameWriter,
|
Http2FrameReader frameReader, Http2FrameWriter frameWriter,
|
||||||
Http2InboundFlowController inboundFlow, Http2OutboundFlowController outboundFlow,
|
Http2InboundFlowController inboundFlow, Http2OutboundFlowController outboundFlow,
|
||||||
@ -107,16 +101,10 @@ public class DelegatingHttp2ConnectionHandler extends AbstractHttp2ConnectionHan
|
|||||||
return super.writePushPromise(ctx, promise, streamId, promisedStreamId, headers, padding);
|
return super.writePushPromise(ctx, promise, streamId, promisedStreamId, headers, padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise,
|
|
||||||
int streamId, long maxAge, int port, ByteBuf protocolId, String host, String origin) {
|
|
||||||
return super.writeAltSvc(ctx, promise, streamId, maxAge, port, protocolId, host, origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception {
|
boolean endOfStream, boolean endOfSegment) throws Http2Exception {
|
||||||
observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment, compressed);
|
observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -178,13 +166,8 @@ public class DelegatingHttp2ConnectionHandler extends AbstractHttp2ConnectionHan
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
|
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
|
||||||
ByteBuf protocolId, String host, String origin) throws Http2Exception {
|
ByteBuf payload) {
|
||||||
observer.onAltSvcRead(ctx, streamId, maxAge, port, protocolId, host, origin);
|
observer.onUnknownFrame(ctx, frameType, streamId, flags, payload);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
|
|
||||||
observer.onBlockedRead(ctx, streamId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import io.netty.handler.codec.base64.Base64;
|
|||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
import io.netty.handler.codec.http.HttpClientUpgradeHandler;
|
import io.netty.handler.codec.http.HttpClientUpgradeHandler;
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
|
import io.netty.util.collection.IntObjectMap;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -103,8 +104,12 @@ public class Http2ClientUpgradeCodec implements HttpClientUpgradeHandler.Upgrade
|
|||||||
Http2Settings settings = connectionHandler.settings();
|
Http2Settings settings = connectionHandler.settings();
|
||||||
|
|
||||||
// Serialize the payload of the SETTINGS frame.
|
// Serialize the payload of the SETTINGS frame.
|
||||||
buf = ctx.alloc().buffer(calcSettingsPayloadLength(settings));
|
int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
|
||||||
writeSettingsPayload(settings, buf);
|
buf = ctx.alloc().buffer(payloadLength);
|
||||||
|
for (IntObjectMap.Entry<Long> entry : settings.entries()) {
|
||||||
|
writeUnsignedShort(entry.key(), buf);
|
||||||
|
writeUnsignedInt(entry.value(), buf);
|
||||||
|
}
|
||||||
|
|
||||||
// Base64 encode the payload and then convert to a string for the header.
|
// Base64 encode the payload and then convert to a string for the header.
|
||||||
encodedBuf = Base64.encode(buf, URL_SAFE);
|
encodedBuf = Base64.encode(buf, URL_SAFE);
|
||||||
|
@ -15,16 +15,15 @@
|
|||||||
|
|
||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Exception.format;
|
||||||
|
import static io.netty.util.CharsetUtil.UTF_8;
|
||||||
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;
|
||||||
import io.netty.channel.ChannelPromise;
|
import io.netty.channel.ChannelPromise;
|
||||||
import io.netty.handler.codec.http2.Http2StreamRemovalPolicy.Action;
|
import io.netty.handler.codec.http2.Http2StreamRemovalPolicy.Action;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2Error.*;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Exception.*;
|
|
||||||
import static io.netty.util.CharsetUtil.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants and utility method used for encoding/decoding HTTP2 frames.
|
* Constants and utility method used for encoding/decoding HTTP2 frames.
|
||||||
*/
|
*/
|
||||||
@ -36,7 +35,7 @@ public final class Http2CodecUtil {
|
|||||||
public static final int CONNECTION_STREAM_ID = 0;
|
public static final int CONNECTION_STREAM_ID = 0;
|
||||||
public static final int HTTP_UPGRADE_STREAM_ID = 1;
|
public static final int HTTP_UPGRADE_STREAM_ID = 1;
|
||||||
public static final String HTTP_UPGRADE_SETTINGS_HEADER = "HTTP2-Settings";
|
public static final String HTTP_UPGRADE_SETTINGS_HEADER = "HTTP2-Settings";
|
||||||
public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-12";
|
public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-13";
|
||||||
|
|
||||||
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;
|
||||||
@ -45,19 +44,19 @@ public final class Http2CodecUtil {
|
|||||||
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 int SETTING_ENTRY_LENGTH = 5;
|
public static final int SETTING_ENTRY_LENGTH = 6;
|
||||||
public static final int PRIORITY_ENTRY_LENGTH = 5;
|
public static final int PRIORITY_ENTRY_LENGTH = 5;
|
||||||
public static final int INT_FIELD_LENGTH = 4;
|
public static final int INT_FIELD_LENGTH = 4;
|
||||||
public static final short MAX_WEIGHT = 256;
|
public static final short MAX_WEIGHT = (short) 256;
|
||||||
public static final short MIN_WEIGHT = 1;
|
public static final short MIN_WEIGHT = (short) 1;
|
||||||
|
|
||||||
public static final short SETTINGS_HEADER_TABLE_SIZE = 1;
|
public static final int SETTINGS_HEADER_TABLE_SIZE = 1;
|
||||||
public static final short SETTINGS_ENABLE_PUSH = 2;
|
public static final int SETTINGS_ENABLE_PUSH = 2;
|
||||||
public static final short SETTINGS_MAX_CONCURRENT_STREAMS = 3;
|
public static final int SETTINGS_MAX_CONCURRENT_STREAMS = 3;
|
||||||
public static final short SETTINGS_INITIAL_WINDOW_SIZE = 4;
|
public static final int SETTINGS_INITIAL_WINDOW_SIZE = 4;
|
||||||
public static final short SETTINGS_COMPRESS_DATA = 5;
|
|
||||||
|
|
||||||
public static final int DEFAULT_FLOW_CONTROL_WINDOW_SIZE = 65535;
|
public static final int DEFAULT_WINDOW_SIZE = 65535;
|
||||||
|
public static final boolean DEFAULT_ENABLE_PUSH = true;
|
||||||
public static final short DEFAULT_PRIORITY_WEIGHT = 16;
|
public static final short DEFAULT_PRIORITY_WEIGHT = 16;
|
||||||
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 = 8192;
|
public static final int DEFAULT_MAX_HEADER_SIZE = 8192;
|
||||||
@ -163,58 +162,15 @@ public final class Http2CodecUtil {
|
|||||||
/**
|
/**
|
||||||
* Writes an HTTP/2 frame header to the output buffer.
|
* Writes an HTTP/2 frame header to the output buffer.
|
||||||
*/
|
*/
|
||||||
public static void writeFrameHeader(ByteBuf out, int payloadLength, Http2FrameType type,
|
public static void writeFrameHeader(ByteBuf out, int payloadLength, byte type,
|
||||||
Http2Flags flags, int streamId) {
|
Http2Flags flags, int streamId) {
|
||||||
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
||||||
out.writeShort(payloadLength);
|
out.writeShort(payloadLength);
|
||||||
out.writeByte(type.typeCode());
|
out.writeByte(type);
|
||||||
out.writeByte(flags.value());
|
out.writeByte(flags.value());
|
||||||
out.writeInt(streamId);
|
out.writeInt(streamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the HTTP/2 SETTINGS payload length for the serialized representation
|
|
||||||
* of the given settings.
|
|
||||||
*/
|
|
||||||
public static int calcSettingsPayloadLength(Http2Settings settings) {
|
|
||||||
int numFields = 0;
|
|
||||||
numFields += settings.hasAllowCompressedData() ? 1 : 0;
|
|
||||||
numFields += settings.hasMaxHeaderTableSize() ? 1 : 0;
|
|
||||||
numFields += settings.hasInitialWindowSize() ? 1 : 0;
|
|
||||||
numFields += settings.hasMaxConcurrentStreams() ? 1 : 0;
|
|
||||||
numFields += settings.hasPushEnabled() ? 1 : 0;
|
|
||||||
|
|
||||||
return SETTING_ENTRY_LENGTH * numFields;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializes the settings to the output buffer in the format of an HTTP/2 SETTINGS frame
|
|
||||||
* payload.
|
|
||||||
*/
|
|
||||||
public static void writeSettingsPayload(Http2Settings settings, ByteBuf out) {
|
|
||||||
if (settings.hasAllowCompressedData()) {
|
|
||||||
out.writeByte(SETTINGS_COMPRESS_DATA);
|
|
||||||
writeUnsignedInt(settings.allowCompressedData() ? 1L : 0L, out);
|
|
||||||
}
|
|
||||||
if (settings.hasMaxHeaderTableSize()) {
|
|
||||||
out.writeByte(SETTINGS_HEADER_TABLE_SIZE);
|
|
||||||
writeUnsignedInt(settings.maxHeaderTableSize(), out);
|
|
||||||
}
|
|
||||||
if (settings.hasInitialWindowSize()) {
|
|
||||||
out.writeByte(SETTINGS_INITIAL_WINDOW_SIZE);
|
|
||||||
writeUnsignedInt(settings.initialWindowSize(), out);
|
|
||||||
}
|
|
||||||
if (settings.hasMaxConcurrentStreams()) {
|
|
||||||
out.writeByte(SETTINGS_MAX_CONCURRENT_STREAMS);
|
|
||||||
writeUnsignedInt(settings.maxConcurrentStreams(), out);
|
|
||||||
}
|
|
||||||
if (settings.hasPushEnabled()) {
|
|
||||||
// Only write the enable push flag from client endpoints.
|
|
||||||
out.writeByte(SETTINGS_ENABLE_PUSH);
|
|
||||||
writeUnsignedInt(settings.pushEnabled() ? 1L : 0L, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fails the given promise with the cause and then re-throws the cause.
|
* Fails the given promise with the cause and then re-throws the cause.
|
||||||
*/
|
*/
|
||||||
|
@ -177,16 +177,6 @@ public interface Http2Connection {
|
|||||||
*/
|
*/
|
||||||
void maxStreams(int maxStreams);
|
void maxStreams(int maxStreams);
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether or not this endpoint allows compression.
|
|
||||||
*/
|
|
||||||
boolean allowCompressedData();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether or not this endpoint allows compression.
|
|
||||||
*/
|
|
||||||
void allowCompressedData(boolean allow);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the ID of the stream last successfully created by this endpoint.
|
* Gets the ID of the stream last successfully created by this endpoint.
|
||||||
*/
|
*/
|
||||||
|
@ -19,19 +19,19 @@ package io.netty.handler.codec.http2;
|
|||||||
* All error codes identified by the HTTP/2 spec.
|
* All error codes identified by the HTTP/2 spec.
|
||||||
*/
|
*/
|
||||||
public enum Http2Error {
|
public enum Http2Error {
|
||||||
NO_ERROR(0),
|
NO_ERROR(0x0),
|
||||||
PROTOCOL_ERROR(1),
|
PROTOCOL_ERROR(0x1),
|
||||||
INTERNAL_ERROR(2),
|
INTERNAL_ERROR(0x2),
|
||||||
FLOW_CONTROL_ERROR(3),
|
FLOW_CONTROL_ERROR(0x3),
|
||||||
SETTINGS_TIMEOUT(4),
|
SETTINGS_TIMEOUT(0x4),
|
||||||
STREAM_CLOSED(5),
|
STREAM_CLOSED(0x5),
|
||||||
FRAME_SIZE_ERROR(6),
|
FRAME_SIZE_ERROR(0x6),
|
||||||
REFUSED_STREAM(7),
|
REFUSED_STREAM(0x7),
|
||||||
CANCEL(8),
|
CANCEL(0x8),
|
||||||
COMPRESSION_ERROR(9),
|
COMPRESSION_ERROR(0x9),
|
||||||
CONNECT_ERROR(10),
|
CONNECT_ERROR(0xA),
|
||||||
ENHANCE_YOUR_CALM(11),
|
ENHANCE_YOUR_CALM(0xB),
|
||||||
INADEQUATE_SECURITY(12);
|
INADEQUATE_SECURITY(0xC);
|
||||||
|
|
||||||
private final int code;
|
private final int code;
|
||||||
|
|
||||||
|
@ -15,28 +15,20 @@
|
|||||||
|
|
||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides utility methods for accessing specific flags as defined by the HTTP/2 spec.
|
* Provides utility methods for accessing specific flags as defined by the HTTP/2 spec.
|
||||||
*/
|
*/
|
||||||
public class Http2Flags {
|
public final class Http2Flags {
|
||||||
public static final Http2Flags EMPTY = new Http2Flags();
|
|
||||||
public static final Http2Flags ACK_ONLY = new Builder().ack(true).build();
|
|
||||||
|
|
||||||
public static final short END_STREAM = 0x1;
|
public static final short END_STREAM = 0x1;
|
||||||
public static final short END_SEGMENT = 0x2;
|
public static final short END_SEGMENT = 0x2;
|
||||||
public static final short END_HEADERS = 0x4;
|
public static final short END_HEADERS = 0x4;
|
||||||
public static final short ACK = 0x1;
|
public static final short ACK = 0x1;
|
||||||
public static final short PAD_LOW = 0x8;
|
public static final short PADDED = 0x8;
|
||||||
public static final short PAD_HIGH = 0x10;
|
|
||||||
public static final short PRIORITY = 0x20;
|
public static final short PRIORITY = 0x20;
|
||||||
public static final short COMPRESSED = 0x20;
|
|
||||||
|
|
||||||
private final short value;
|
private short value;
|
||||||
|
|
||||||
private Http2Flags() {
|
public Http2Flags() {
|
||||||
this((short) 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Http2Flags(short value) {
|
public Http2Flags(short value) {
|
||||||
@ -55,7 +47,7 @@ public class Http2Flags {
|
|||||||
* frames.
|
* frames.
|
||||||
*/
|
*/
|
||||||
public boolean endOfStream() {
|
public boolean endOfStream() {
|
||||||
return endOfStream(value);
|
return isFlagSet(END_STREAM);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,7 +55,7 @@ public class Http2Flags {
|
|||||||
* frames.
|
* frames.
|
||||||
*/
|
*/
|
||||||
public boolean endOfSegment() {
|
public boolean endOfSegment() {
|
||||||
return endOfSegment(value);
|
return isFlagSet(END_SEGMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,7 +63,7 @@ public class Http2Flags {
|
|||||||
* PUSH_PROMISE, and CONTINUATION frames.
|
* PUSH_PROMISE, and CONTINUATION frames.
|
||||||
*/
|
*/
|
||||||
public boolean endOfHeaders() {
|
public boolean endOfHeaders() {
|
||||||
return endOfHeaders(value);
|
return isFlagSet(END_HEADERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,7 +71,7 @@ public class Http2Flags {
|
|||||||
* dependency, and weight fields in a HEADERS frame.
|
* dependency, and weight fields in a HEADERS frame.
|
||||||
*/
|
*/
|
||||||
public boolean priorityPresent() {
|
public boolean priorityPresent() {
|
||||||
return priorityPresent(value);
|
return isFlagSet(PRIORITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,46 +79,15 @@ public class Http2Flags {
|
|||||||
* SETTINGS and PING frames.
|
* SETTINGS and PING frames.
|
||||||
*/
|
*/
|
||||||
public boolean ack() {
|
public boolean ack() {
|
||||||
return ack(value);
|
return isFlagSet(ACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For DATA frames, indicates that the data is compressed using gzip compression.
|
* For frames that include padding, indicates if the {@link #PADDED} field is present. Only
|
||||||
*/
|
|
||||||
public boolean compressed() {
|
|
||||||
return compressed(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For frames that include padding, indicates if the {@link #PAD_LOW} field is present. Only
|
|
||||||
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
|
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
|
||||||
*/
|
*/
|
||||||
public boolean padLowPresent() {
|
public boolean paddingPresent() {
|
||||||
return padLowPresent(value);
|
return isFlagSet(PADDED);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For frames that include padding, indicates if the {@link #PAD_HIGH} field is present. Only
|
|
||||||
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
|
|
||||||
*/
|
|
||||||
public boolean padHighPresent() {
|
|
||||||
return padHighPresent(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether the padding flags are set properly. If pad high is set, pad low must also
|
|
||||||
* be set.
|
|
||||||
*/
|
|
||||||
public boolean isPaddingLengthValid() {
|
|
||||||
return isPaddingLengthValid(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the number of bytes expected in the padding length field of the payload. This is
|
|
||||||
* determined by the {@link #padHighPresent()} and {@link #padLowPresent()} flags.
|
|
||||||
*/
|
|
||||||
public int getNumPaddingLengthBytes() {
|
|
||||||
return getNumPaddingLengthBytes(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,14 +95,81 @@ public class Http2Flags {
|
|||||||
* by the {@link #priorityPresent()} flag.
|
* by the {@link #priorityPresent()} flag.
|
||||||
*/
|
*/
|
||||||
public int getNumPriorityBytes() {
|
public int getNumPriorityBytes() {
|
||||||
return getNumPriorityBytes(value);
|
return priorityPresent() ? 5 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the variable-length padding length field from the payload.
|
* Gets the length in bytes of the padding presence field expected in the payload. This is
|
||||||
|
* determined by the {@link #paddingPresent()} flag.
|
||||||
*/
|
*/
|
||||||
public int readPaddingLength(ByteBuf payload) {
|
public int getPaddingPresenceFieldLength() {
|
||||||
return readPaddingLength(value, payload);
|
return paddingPresent() ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link #END_STREAM} flag.
|
||||||
|
*/
|
||||||
|
public Http2Flags endOfStream(boolean endOfStream) {
|
||||||
|
return setFlag(endOfStream, END_STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link #END_SEGMENT} flag.
|
||||||
|
*/
|
||||||
|
public Http2Flags endOfSegment(boolean endOfSegment) {
|
||||||
|
return setFlag(endOfSegment, END_SEGMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link #END_HEADERS} flag.
|
||||||
|
*/
|
||||||
|
public Http2Flags endOfHeaders(boolean endOfHeaders) {
|
||||||
|
return setFlag(endOfHeaders, END_HEADERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link #PRIORITY} flag.
|
||||||
|
*/
|
||||||
|
public Http2Flags priorityPresent(boolean priorityPresent) {
|
||||||
|
return setFlag(priorityPresent, PRIORITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link #PADDED} flag.
|
||||||
|
*/
|
||||||
|
public Http2Flags paddingPresent(boolean paddingPresent) {
|
||||||
|
return setFlag(paddingPresent, PADDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link #ACK} flag.
|
||||||
|
*/
|
||||||
|
public Http2Flags ack(boolean ack) {
|
||||||
|
return setFlag(ack, ACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic method to set any flag.
|
||||||
|
* @param on if the flag should be enabled or disabled.
|
||||||
|
* @param mask the mask that identifies the bit for the flag.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public Http2Flags setFlag(boolean on, short mask) {
|
||||||
|
if (on) {
|
||||||
|
value |= mask;
|
||||||
|
} else {
|
||||||
|
value &= ~mask;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not a particular flag is set.
|
||||||
|
* @param mask the mask identifying the bit for the particular flag being tested
|
||||||
|
* @return {@code true} if the flag is set
|
||||||
|
*/
|
||||||
|
public boolean isFlagSet(short mask) {
|
||||||
|
return (value & mask) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -186,298 +214,10 @@ public class Http2Flags {
|
|||||||
if (endOfSegment()) {
|
if (endOfSegment()) {
|
||||||
builder.append("END_OF_SEGMENT,");
|
builder.append("END_OF_SEGMENT,");
|
||||||
}
|
}
|
||||||
if (padHighPresent()) {
|
if (paddingPresent()) {
|
||||||
builder.append("PAD_HIGH,");
|
builder.append("PADDING_PRESENT,");
|
||||||
}
|
|
||||||
if (padLowPresent()) {
|
|
||||||
builder.append("PAD_LOW,");
|
|
||||||
}
|
}
|
||||||
builder.append(')');
|
builder.append(')');
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for creating a new {@link Builder} instance.
|
|
||||||
*/
|
|
||||||
public static Builder newBuilder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builder for instances of {@link Http2Flags}.
|
|
||||||
*/
|
|
||||||
public static final class Builder {
|
|
||||||
private short value;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link #END_STREAM} flag.
|
|
||||||
*/
|
|
||||||
public Builder endOfStream(boolean endOfStream) {
|
|
||||||
return setFlag(endOfStream, END_STREAM);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link #END_SEGMENT} flag.
|
|
||||||
*/
|
|
||||||
public Builder endOfSegment(boolean endOfSegment) {
|
|
||||||
return setFlag(endOfSegment, END_SEGMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link #END_HEADERS} flag.
|
|
||||||
*/
|
|
||||||
public Builder endOfHeaders(boolean endOfHeaders) {
|
|
||||||
return setFlag(endOfHeaders, END_HEADERS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link #PRIORITY} flag.
|
|
||||||
*/
|
|
||||||
public Builder priorityPresent(boolean priorityPresent) {
|
|
||||||
return setFlag(priorityPresent, PRIORITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link #ACK} flag.
|
|
||||||
*/
|
|
||||||
public Builder ack(boolean ack) {
|
|
||||||
return setFlag(ack, ACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link #COMPRESSED} flag.
|
|
||||||
*/
|
|
||||||
public Builder compressed(boolean compressed) {
|
|
||||||
return setFlag(compressed, COMPRESSED);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the padding flags in the given flags value as appropriate based on the padding
|
|
||||||
* length.
|
|
||||||
*/
|
|
||||||
public Builder setPaddingFlags(int paddingLength) {
|
|
||||||
if (paddingLength > 255) {
|
|
||||||
value |= PAD_HIGH;
|
|
||||||
}
|
|
||||||
if (paddingLength > 0) {
|
|
||||||
value |= PAD_LOW;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the {@link #END_STREAM} flag is set. Only applies to DATA and HEADERS
|
|
||||||
* frames.
|
|
||||||
*/
|
|
||||||
public boolean endOfStream() {
|
|
||||||
return Http2Flags.endOfStream(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the {@link #END_SEGMENT} flag is set. Only applies to DATA and HEADERS
|
|
||||||
* frames.
|
|
||||||
*/
|
|
||||||
public boolean endOfSegment() {
|
|
||||||
return Http2Flags.endOfSegment(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the {@link #END_HEADERS} flag is set. Only applies for HEADERS,
|
|
||||||
* PUSH_PROMISE, and CONTINUATION frames.
|
|
||||||
*/
|
|
||||||
public boolean endOfHeaders() {
|
|
||||||
return Http2Flags.endOfHeaders(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the flag is set indicating the presence of the exclusive, stream
|
|
||||||
* dependency, and weight fields in a HEADERS frame.
|
|
||||||
*/
|
|
||||||
public boolean priorityPresent() {
|
|
||||||
return Http2Flags.priorityPresent(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the flag is set indicating that this frame is an ACK. Only applies for
|
|
||||||
* SETTINGS and PING frames.
|
|
||||||
*/
|
|
||||||
public boolean ack() {
|
|
||||||
return Http2Flags.ack(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For DATA frames, indicates that the data is compressed using gzip compression.
|
|
||||||
*/
|
|
||||||
public boolean compressed() {
|
|
||||||
return Http2Flags.compressed(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For frames that include padding, indicates if the {@link #PAD_LOW} field is present. Only
|
|
||||||
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
|
|
||||||
*/
|
|
||||||
public boolean padLowPresent() {
|
|
||||||
return Http2Flags.padLowPresent(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For frames that include padding, indicates if the {@link #PAD_HIGH} field is present. Only
|
|
||||||
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
|
|
||||||
*/
|
|
||||||
public boolean padHighPresent() {
|
|
||||||
return Http2Flags.padHighPresent(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether the padding flags are set properly. If pad high is set, pad low must also
|
|
||||||
* be set.
|
|
||||||
*/
|
|
||||||
public boolean isPaddingLengthValid() {
|
|
||||||
return Http2Flags.isPaddingLengthValid(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the number of bytes expected in the padding length field of the payload. This is
|
|
||||||
* determined by the {@link #padHighPresent()} and {@link #padLowPresent()} flags.
|
|
||||||
*/
|
|
||||||
public int getNumPaddingLengthBytes() {
|
|
||||||
return Http2Flags.getNumPaddingLengthBytes(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the number of bytes expected for the priority fields of the payload. This is determined
|
|
||||||
* by the {@link #priorityPresent()} flag.
|
|
||||||
*/
|
|
||||||
public int getNumPriorityBytes() {
|
|
||||||
return Http2Flags.getNumPriorityBytes(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the variable-length padding length field from the payload.
|
|
||||||
*/
|
|
||||||
public int readPaddingLength(ByteBuf payload) {
|
|
||||||
return Http2Flags.readPaddingLength(value, payload);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Builds a new {@link Http2Flags} instance.
|
|
||||||
*/
|
|
||||||
public Http2Flags build() {
|
|
||||||
return new Http2Flags(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder setFlag(boolean on, short mask) {
|
|
||||||
if (on) {
|
|
||||||
value |= mask;
|
|
||||||
} else {
|
|
||||||
value &= ~mask;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the {@link #END_STREAM} flag is set. Only applies to DATA and HEADERS
|
|
||||||
* frames.
|
|
||||||
*/
|
|
||||||
public static boolean endOfStream(short value) {
|
|
||||||
return isFlagSet(value, END_STREAM);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the {@link #END_SEGMENT} flag is set. Only applies to DATA and HEADERS
|
|
||||||
* frames.
|
|
||||||
*/
|
|
||||||
public static boolean endOfSegment(short value) {
|
|
||||||
return isFlagSet(value, END_SEGMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the {@link #END_HEADERS} flag is set. Only applies for HEADERS,
|
|
||||||
* PUSH_PROMISE, and CONTINUATION frames.
|
|
||||||
*/
|
|
||||||
public static boolean endOfHeaders(short value) {
|
|
||||||
return isFlagSet(value, END_HEADERS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the flag is set indicating the presence of the exclusive, stream
|
|
||||||
* dependency, and weight fields in a HEADERS frame.
|
|
||||||
*/
|
|
||||||
public static boolean priorityPresent(short value) {
|
|
||||||
return isFlagSet(value, PRIORITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the flag is set indicating that this frame is an ACK. Only applies for
|
|
||||||
* SETTINGS and PING frames.
|
|
||||||
*/
|
|
||||||
public static boolean ack(short value) {
|
|
||||||
return isFlagSet(value, ACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For DATA frames, indicates that the data is compressed using gzip compression.
|
|
||||||
*/
|
|
||||||
public static boolean compressed(short value) {
|
|
||||||
return isFlagSet(value, COMPRESSED);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For frames that include padding, indicates if the {@link #PAD_LOW} field is present. Only
|
|
||||||
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
|
|
||||||
*/
|
|
||||||
public static boolean padLowPresent(short value) {
|
|
||||||
return isFlagSet(value, PAD_LOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For frames that include padding, indicates if the {@link #PAD_HIGH} field is present. Only
|
|
||||||
* applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
|
|
||||||
*/
|
|
||||||
public static boolean padHighPresent(short value) {
|
|
||||||
return isFlagSet(value, PAD_HIGH);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether the padding flags are set properly. If pad high is set, pad low must also
|
|
||||||
* be set.
|
|
||||||
*/
|
|
||||||
public static boolean isPaddingLengthValid(short value) {
|
|
||||||
return padHighPresent(value) ? padLowPresent(value) : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the number of bytes expected in the padding length field of the payload. This is
|
|
||||||
* determined by the {@link #padHighPresent()} and {@link #padLowPresent()} flags.
|
|
||||||
*/
|
|
||||||
public static int getNumPaddingLengthBytes(short value) {
|
|
||||||
return (padHighPresent(value) ? 1 : 0) + (padLowPresent(value) ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the number of bytes expected for the priority fields of the payload. This is determined
|
|
||||||
* by the {@link #priorityPresent()} flag.
|
|
||||||
*/
|
|
||||||
public static int getNumPriorityBytes(short value) {
|
|
||||||
return priorityPresent(value) ? 5 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the variable-length padding length field from the payload.
|
|
||||||
*/
|
|
||||||
public static int readPaddingLength(short value, ByteBuf payload) {
|
|
||||||
int paddingLength = 0;
|
|
||||||
if (padHighPresent(value)) {
|
|
||||||
paddingLength += payload.readUnsignedByte() * 256;
|
|
||||||
}
|
|
||||||
if (padLowPresent(value)) {
|
|
||||||
paddingLength += payload.readUnsignedByte();
|
|
||||||
}
|
|
||||||
|
|
||||||
return paddingLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isFlagSet(short value, short mask) {
|
|
||||||
return (value & mask) != 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ public class Http2FrameAdapter implements Http2FrameObserver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception {
|
boolean endOfStream, boolean endOfSegment) throws Http2Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -81,11 +81,7 @@ public class Http2FrameAdapter implements Http2FrameObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
|
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
|
||||||
ByteBuf protocolId, String host, String origin) throws Http2Exception {
|
ByteBuf payload) {
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.ByteBufUtil;
|
||||||
import io.netty.channel.ChannelHandlerAdapter;
|
import io.netty.channel.ChannelHandlerAdapter;
|
||||||
import io.netty.util.internal.logging.InternalLogLevel;
|
import io.netty.util.internal.logging.InternalLogLevel;
|
||||||
import io.netty.util.internal.logging.InternalLogger;
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
@ -50,10 +51,10 @@ public class Http2FrameLogger extends ChannelHandlerAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void logData(Direction direction, int streamId, ByteBuf data, int padding,
|
public void logData(Direction direction, int streamId, ByteBuf data, int padding,
|
||||||
boolean endStream, boolean endSegment, boolean compressed) {
|
boolean endStream, boolean endSegment) {
|
||||||
log(direction,
|
log(direction,
|
||||||
"DATA: streamId=%d, dataLen=%d, padding=%d, endStream=%b, endSegment=%b, compressed=%b",
|
"DATA: streamId=%d, padding=%d, endStream=%b, endSegment=%b, length=%d, bytes=%s",
|
||||||
streamId, data.readableBytes(), padding, endStream, endSegment, compressed);
|
streamId, padding, endStream, endSegment, data.readableBytes(), ByteBufUtil.hexDump(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logHeaders(Direction direction, int streamId, Http2Headers headers, int padding,
|
public void logHeaders(Direction direction, int streamId, Http2Headers headers, int padding,
|
||||||
@ -90,11 +91,11 @@ public class Http2FrameLogger extends ChannelHandlerAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void logPing(Direction direction, ByteBuf data) {
|
public void logPing(Direction direction, ByteBuf data) {
|
||||||
log(direction, "PING: ack=false, dataLen=%d", data.readableBytes());
|
log(direction, "PING: ack=false, length=%d, bytes=%s", data.readableBytes(), ByteBufUtil.hexDump(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logPingAck(Direction direction, ByteBuf data) {
|
public void logPingAck(Direction direction, ByteBuf data) {
|
||||||
log(direction, "PING: ack=true, dataLen=%d", data.readableBytes());
|
log(direction, "PING: ack=true, length=%d, bytes=%s", data.readableBytes(), ByteBufUtil.hexDump(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logPushPromise(Direction direction, int streamId, int promisedStreamId,
|
public void logPushPromise(Direction direction, int streamId, int promisedStreamId,
|
||||||
@ -104,8 +105,8 @@ public class Http2FrameLogger extends ChannelHandlerAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void logGoAway(Direction direction, int lastStreamId, long errorCode, ByteBuf debugData) {
|
public void logGoAway(Direction direction, int lastStreamId, long errorCode, ByteBuf debugData) {
|
||||||
log(direction, "GO_AWAY: lastStreamId=%d, errorCode=%d, dataLen=%d", lastStreamId,
|
log(direction, "GO_AWAY: lastStreamId=%d, errorCode=%d, length=%d, bytes=%s", lastStreamId,
|
||||||
errorCode, debugData.readableBytes());
|
errorCode, debugData.readableBytes(), ByteBufUtil.hexDump(debugData));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logWindowsUpdate(Direction direction, int streamId, int windowSizeIncrement) {
|
public void logWindowsUpdate(Direction direction, int streamId, int windowSizeIncrement) {
|
||||||
@ -113,15 +114,9 @@ public class Http2FrameLogger extends ChannelHandlerAdapter {
|
|||||||
windowSizeIncrement);
|
windowSizeIncrement);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logAltSvc(Direction direction, int streamId, long maxAge, int port,
|
public void logUnknownFrame(Direction direction, byte frameType, int streamId, Http2Flags flags, ByteBuf data) {
|
||||||
ByteBuf protocolId, String host, String origin) {
|
log(direction, "UNKNOWN: frameType=%d, streamId=%d, flags=%d, length=%d, bytes=%s",
|
||||||
log(direction,
|
frameType & 0xFF, streamId, flags.value(), data.readableBytes(), ByteBufUtil.hexDump(data));
|
||||||
"ALT_SVC: streamId=%d, maxAge=%d, port=%d, protocolIdLen=%d, host=%s, origin=%s",
|
|
||||||
streamId, maxAge, port, protocolId.readableBytes(), host, origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logBlocked(Direction direction, int streamId) {
|
|
||||||
log(direction, "BLOCKED: streamId=%d", streamId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void log(Direction direction, String format, Object... args) {
|
private void log(Direction direction, String format, Object... args) {
|
||||||
|
@ -34,10 +34,9 @@ public interface Http2FrameObserver {
|
|||||||
* @param endOfStream Indicates whether this is the last frame to be sent from the remote
|
* @param endOfStream Indicates whether this is the last frame to be sent from the remote
|
||||||
* endpoint for this stream.
|
* endpoint for this stream.
|
||||||
* @param endOfSegment Indicates whether this frame is the end of the current segment.
|
* @param endOfSegment Indicates whether this frame is the end of the current segment.
|
||||||
* @param compressed Indicates whether or not the payload is compressed with gzip encoding.
|
|
||||||
*/
|
*/
|
||||||
void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception;
|
boolean endOfStream, boolean endOfSegment) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles an inbound HEADERS frame.
|
* Handles an inbound HEADERS frame.
|
||||||
@ -162,26 +161,13 @@ public interface Http2FrameObserver {
|
|||||||
throws Http2Exception;
|
throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles an inbound ALT_SVC frame.
|
* Handler for a frame not defined by the HTTP/2 spec.
|
||||||
*
|
*
|
||||||
* @param ctx the context from the handler where the frame was read.
|
* @param ctx the context from the handler where the frame was read.
|
||||||
* @param streamId the stream.
|
* @param frameType the frame type from the HTTP/2 header.
|
||||||
* @param maxAge the freshness lifetime of the alternative service association.
|
* @param streamId the stream the frame was sent on.
|
||||||
* @param port the port that the alternative service is available upon.
|
* @param flags the flags in the frame header.
|
||||||
* @param protocolId the ALPN protocol identifier of the alternative service. If this buffer
|
* @param payload the payload of the frame.
|
||||||
* needs to be retained by the observer they must make a copy.
|
|
||||||
* @param host the host that the alternative service is available upon.
|
|
||||||
* @param origin an optional origin that the alternative service is available upon. May be
|
|
||||||
* {@code null}.
|
|
||||||
*/
|
*/
|
||||||
void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
|
void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload);
|
||||||
ByteBuf protocolId, String host, String origin) throws Http2Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles an inbound BLOCKED frame.
|
|
||||||
*
|
|
||||||
* @param ctx the context from the handler where the frame was read.
|
|
||||||
* @param streamId the stream that is blocked or 0 if the entire connection is blocked.
|
|
||||||
*/
|
|
||||||
void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception;
|
|
||||||
}
|
}
|
||||||
|
@ -36,12 +36,12 @@ public interface Http2FrameReader extends Closeable {
|
|||||||
/**
|
/**
|
||||||
* Sets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
|
* Sets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
|
||||||
*/
|
*/
|
||||||
void maxHeaderTableSize(int max);
|
void maxHeaderTableSize(long max);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
|
* Gets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
|
||||||
*/
|
*/
|
||||||
int maxHeaderTableSize();
|
long maxHeaderTableSize();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes this reader and frees any allocated resources.
|
* Closes this reader and frees any allocated resources.
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014 The Netty Project
|
|
||||||
*
|
|
||||||
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License. You may obtain a
|
|
||||||
* copy of the License at:
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
|
||||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
||||||
* or implied. See the License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.netty.handler.codec.http2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enumeration of all frame types defined by the HTTP/2 specification.
|
|
||||||
*/
|
|
||||||
public enum Http2FrameType {
|
|
||||||
DATA((short) 0x0),
|
|
||||||
HEADERS((short) 0x1),
|
|
||||||
PRIORITY((short) 0x2),
|
|
||||||
RST_STREAM((short) 0x3),
|
|
||||||
SETTINGS((short) 0x4),
|
|
||||||
PUSH_PROMISE((short) 0x5),
|
|
||||||
PING((short) 0x6),
|
|
||||||
GO_AWAY((short) 0x7),
|
|
||||||
WINDOW_UPDATE((short) 0x8),
|
|
||||||
CONTINUATION((short) 0x9),
|
|
||||||
ALT_SVC((short) 0xA),
|
|
||||||
BLOCKED((short) 0xB);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an array indexed by the frame type code for fast lookup of the enum value.
|
|
||||||
*/
|
|
||||||
private static final Http2FrameType[] codeToTypeMap;
|
|
||||||
static {
|
|
||||||
int maxIndex = 0;
|
|
||||||
for (Http2FrameType type : Http2FrameType.values()) {
|
|
||||||
maxIndex = Math.max(maxIndex, type.typeCode());
|
|
||||||
}
|
|
||||||
codeToTypeMap = new Http2FrameType[maxIndex + 1];
|
|
||||||
for (Http2FrameType type : Http2FrameType.values()) {
|
|
||||||
codeToTypeMap[type.typeCode()] = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final short code;
|
|
||||||
|
|
||||||
Http2FrameType(short code) {
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the code used to represent this frame type on the wire.
|
|
||||||
*/
|
|
||||||
public short typeCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks up the frame type by it's type code.
|
|
||||||
*/
|
|
||||||
public static Http2FrameType forTypeCode(short typeCode) {
|
|
||||||
Http2FrameType type = null;
|
|
||||||
if (typeCode >= 0 && typeCode < codeToTypeMap.length) {
|
|
||||||
type = codeToTypeMap[typeCode];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == null) {
|
|
||||||
throw new IllegalArgumentException("Unsupported typeCode: " + typeCode);
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License. You may obtain a
|
||||||
|
* copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
* or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry of all standard frame types defined by the HTTP/2 specification.
|
||||||
|
*/
|
||||||
|
public final class Http2FrameTypes {
|
||||||
|
public static final byte DATA = 0x0;
|
||||||
|
public static final byte HEADERS = 0x1;
|
||||||
|
public static final byte PRIORITY = 0x2;
|
||||||
|
public static final byte RST_STREAM = 0x3;
|
||||||
|
public static final byte SETTINGS = 0x4;
|
||||||
|
public static final byte PUSH_PROMISE = 0x5;
|
||||||
|
public static final byte PING = 0x6;
|
||||||
|
public static final byte GO_AWAY = 0x7;
|
||||||
|
public static final byte WINDOW_UPDATE = 0x8;
|
||||||
|
public static final byte CONTINUATION = 0x9;
|
||||||
|
|
||||||
|
private Http2FrameTypes() {
|
||||||
|
}
|
||||||
|
}
|
@ -37,11 +37,10 @@ public interface Http2FrameWriter extends Closeable {
|
|||||||
* @param padding the amount of padding to be added to the end of the frame
|
* @param padding the amount of padding to be added to the end of the frame
|
||||||
* @param endStream indicates if this is the last frame to be sent for the stream.
|
* @param endStream indicates if this is the last frame to be sent for the stream.
|
||||||
* @param endSegment indicates if this is the last frame in the current segment.
|
* @param endSegment indicates if this is the last frame in the current segment.
|
||||||
* @param compressed indicates whether the data is compressed using gzip encoding.
|
|
||||||
* @return the future for the write.
|
* @return the future for the write.
|
||||||
*/
|
*/
|
||||||
ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
|
ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
|
||||||
ByteBuf data, int padding, boolean endStream, boolean endSegment, boolean compressed);
|
ByteBuf data, int padding, boolean endStream, boolean endSegment);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a HEADERS frame to the remote endpoint.
|
* Writes a HEADERS frame to the remote endpoint.
|
||||||
@ -179,31 +178,18 @@ public interface Http2FrameWriter extends Closeable {
|
|||||||
int streamId, int windowSizeIncrement);
|
int streamId, int windowSizeIncrement);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a ALT_SVC frame to the remote endpoint.
|
* Generic write method for any HTTP/2 frame. This allows writing of non-standard frames.
|
||||||
*
|
*
|
||||||
* @param ctx the context to use for writing.
|
* @param ctx the context to use for writing.
|
||||||
* @param promise the promise for the write.
|
* @param promise the promise for the write.
|
||||||
* @param streamId the stream.
|
* @param frameType the frame type identifier.
|
||||||
* @param maxAge the freshness lifetime of the alternative service association.
|
* @param streamId the stream for which to send the frame.
|
||||||
* @param port the port that the alternative service is available upon.
|
* @param flags the flags to write for this frame.
|
||||||
* @param protocolId the ALPN protocol identifier of the alternative service.
|
* @param payload the payload to write for this frame.
|
||||||
* @param host the host that the alternative service is available upon.
|
|
||||||
* @param origin an optional origin that the alternative service is available upon. May be
|
|
||||||
* {@code null}.
|
|
||||||
* @return the future for the write.
|
* @return the future for the write.
|
||||||
*/
|
*/
|
||||||
ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
|
ChannelFuture writeFrame(ChannelHandlerContext ctx, ChannelPromise promise, byte frameType,
|
||||||
long maxAge, int port, ByteBuf protocolId, String host, String origin);
|
int streamId, Http2Flags flags, ByteBuf payload);
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a BLOCKED frame to the remote endpoint.
|
|
||||||
*
|
|
||||||
* @param ctx the context to use for writing.
|
|
||||||
* @param promise the promise for the write.
|
|
||||||
* @param streamId the stream that is blocked or 0 if the entire connection is blocked.
|
|
||||||
* @return the future for the write.
|
|
||||||
*/
|
|
||||||
ChannelFuture writeBlocked(ChannelHandlerContext ctx, ChannelPromise promise, int streamId);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes this writer and frees any allocated resources.
|
* Closes this writer and frees any allocated resources.
|
||||||
@ -214,10 +200,10 @@ public interface Http2FrameWriter extends Closeable {
|
|||||||
/**
|
/**
|
||||||
* Sets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
|
* Sets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
|
||||||
*/
|
*/
|
||||||
void maxHeaderTableSize(int max) throws Http2Exception;
|
void maxHeaderTableSize(long max) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
|
* Gets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
|
||||||
*/
|
*/
|
||||||
int maxHeaderTableSize();
|
long maxHeaderTableSize();
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,5 @@ public interface Http2InboundFlowController {
|
|||||||
* @throws Http2Exception thrown if any protocol-related error occurred.
|
* @throws Http2Exception thrown if any protocol-related error occurred.
|
||||||
*/
|
*/
|
||||||
void applyInboundFlowControl(int streamId, ByteBuf data, int padding, boolean endOfStream,
|
void applyInboundFlowControl(int streamId, ByteBuf data, int padding, boolean endOfStream,
|
||||||
boolean endOfSegment, boolean compressed, FrameWriter frameWriter)
|
boolean endOfSegment, FrameWriter frameWriter) throws Http2Exception;
|
||||||
throws Http2Exception;
|
|
||||||
}
|
}
|
||||||
|
@ -46,11 +46,10 @@ public class Http2InboundFrameLogger implements Http2FrameReader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data,
|
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data,
|
||||||
int padding, boolean endOfStream, boolean endOfSegment, boolean compressed)
|
int padding, boolean endOfStream, boolean endOfSegment)
|
||||||
throws Http2Exception {
|
throws Http2Exception {
|
||||||
logger.logData(INBOUND, streamId, data, padding, endOfStream, endOfSegment,
|
logger.logData(INBOUND, streamId, data, padding, endOfStream, endOfSegment);
|
||||||
compressed);
|
observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment);
|
||||||
observer.onDataRead(ctx, streamId, data, padding, endOfStream, endOfSegment, compressed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -132,16 +131,10 @@ public class Http2InboundFrameLogger implements Http2FrameReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge,
|
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
|
||||||
int port, ByteBuf protocolId, String host, String origin) throws Http2Exception {
|
Http2Flags flags, ByteBuf payload) {
|
||||||
logger.logAltSvc(INBOUND, streamId, maxAge, port, protocolId, host, origin);
|
logger.logUnknownFrame(INBOUND, frameType, streamId, flags, payload);
|
||||||
observer.onAltSvcRead(ctx, streamId, maxAge, port, protocolId, host, origin);
|
observer.onUnknownFrame(ctx, frameType, streamId, flags, payload);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
|
|
||||||
logger.logBlocked(INBOUND, streamId);
|
|
||||||
observer.onBlockedRead(ctx, streamId);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -152,12 +145,12 @@ public class Http2InboundFrameLogger implements Http2FrameReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maxHeaderTableSize(int max) {
|
public void maxHeaderTableSize(long max) {
|
||||||
reader.maxHeaderTableSize(max);
|
reader.maxHeaderTableSize(max);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int maxHeaderTableSize() {
|
public long maxHeaderTableSize() {
|
||||||
return reader.maxHeaderTableSize();
|
return reader.maxHeaderTableSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ public abstract class Http2OrHttpChooser extends ByteToMessageDecoder {
|
|||||||
|
|
||||||
public enum SelectedProtocol {
|
public enum SelectedProtocol {
|
||||||
/** Must be updated to match the HTTP/2 draft number. */
|
/** Must be updated to match the HTTP/2 draft number. */
|
||||||
HTTP_2("h2-12"),
|
HTTP_2("h2-13"),
|
||||||
HTTP_1_1("http/1.1"),
|
HTTP_1_1("http/1.1"),
|
||||||
HTTP_1_0("http/1.0"),
|
HTTP_1_0("http/1.0"),
|
||||||
UNKNOWN("Unknown");
|
UNKNOWN("Unknown");
|
||||||
|
@ -64,15 +64,6 @@ public interface Http2OutboundFlowController {
|
|||||||
*/
|
*/
|
||||||
void updateOutboundWindowSize(int streamId, int deltaWindowSize) throws Http2Exception;
|
void updateOutboundWindowSize(int streamId, int deltaWindowSize) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that the given stream or the entire connection is blocked and that no more messages
|
|
||||||
* should be sent.
|
|
||||||
*
|
|
||||||
* @param streamId the stream ID that is blocked or zero if the entire connection is blocked.
|
|
||||||
* @throws Http2Exception thrown if a protocol-related error occurred.
|
|
||||||
*/
|
|
||||||
void setBlocked(int streamId) throws Http2Exception;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the frame with outbound flow control applied. The frame may be written at a later time,
|
* 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.
|
* depending on whether the remote endpoint can receive the frame now.
|
||||||
|
@ -43,10 +43,9 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
|
public ChannelFuture writeData(ChannelHandlerContext ctx, ChannelPromise promise, int streamId,
|
||||||
ByteBuf data, int padding, boolean endStream, boolean endSegment, boolean compressed) {
|
ByteBuf data, int padding, boolean endStream, boolean endSegment) {
|
||||||
logger.logData(OUTBOUND, streamId, data, padding, endStream, endSegment, compressed);
|
logger.logData(OUTBOUND, streamId, data, padding, endStream, endSegment);
|
||||||
return writer.writeData(ctx, promise, streamId, data, padding, endStream, endSegment,
|
return writer.writeData(ctx, promise, streamId, data, padding, endStream, endSegment);
|
||||||
compressed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -121,17 +120,10 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise,
|
public ChannelFuture writeFrame(ChannelHandlerContext ctx, ChannelPromise promise,
|
||||||
int streamId, long maxAge, int port, ByteBuf protocolId, String host, String origin) {
|
byte frameType, int streamId, Http2Flags flags, ByteBuf payload) {
|
||||||
logger.logAltSvc(OUTBOUND, streamId, maxAge, port, protocolId, host, origin);
|
logger.logUnknownFrame(OUTBOUND, frameType, streamId, flags, payload);
|
||||||
return writer.writeAltSvc(ctx, promise, streamId, maxAge, port, protocolId, host, origin);
|
return writer.writeFrame(ctx, promise, frameType, streamId, flags, payload);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture writeBlocked(ChannelHandlerContext ctx, ChannelPromise promise,
|
|
||||||
int streamId) {
|
|
||||||
logger.logBlocked(OUTBOUND, streamId);
|
|
||||||
return writer.writeBlocked(ctx, promise, streamId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -140,12 +132,12 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maxHeaderTableSize(int max) throws Http2Exception {
|
public void maxHeaderTableSize(long max) throws Http2Exception {
|
||||||
writer.maxHeaderTableSize(max);
|
writer.maxHeaderTableSize(max);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int maxHeaderTableSize() {
|
public long maxHeaderTableSize() {
|
||||||
return writer.maxHeaderTableSize();
|
return writer.maxHeaderTableSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,13 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.base64.Base64Dialect.URL_SAFE;
|
||||||
|
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_SETTINGS_HEADER;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.writeFrameHeader;
|
||||||
|
import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
|
||||||
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,12 +34,6 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static io.netty.handler.codec.base64.Base64Dialect.*;
|
|
||||||
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Flags.*;
|
|
||||||
import static io.netty.handler.codec.http2.Http2FrameType.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server-side codec for performing a cleartext upgrade from HTTP/1.x to HTTP/2.
|
* Server-side codec for performing a cleartext upgrade from HTTP/1.x to HTTP/2.
|
||||||
*/
|
*/
|
||||||
@ -138,8 +139,9 @@ public class Http2ServerUpgradeCodec implements HttpServerUpgradeHandler.Upgrade
|
|||||||
final Http2Settings decodedSettings = new Http2Settings();
|
final Http2Settings decodedSettings = new Http2Settings();
|
||||||
frameReader.readFrame(ctx, frame, new Http2FrameAdapter() {
|
frameReader.readFrame(ctx, frame, new Http2FrameAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
|
public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings)
|
||||||
decodedSettings.copy(settings);
|
throws Http2Exception {
|
||||||
|
decodedSettings.copyFrom(settings);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return decodedSettings;
|
return decodedSettings;
|
||||||
@ -153,7 +155,7 @@ public class Http2ServerUpgradeCodec implements HttpServerUpgradeHandler.Upgrade
|
|||||||
*/
|
*/
|
||||||
private static ByteBuf createSettingsFrame(ChannelHandlerContext ctx, ByteBuf payload) {
|
private static ByteBuf createSettingsFrame(ChannelHandlerContext ctx, ByteBuf payload) {
|
||||||
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payload.readableBytes());
|
ByteBuf frame = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payload.readableBytes());
|
||||||
writeFrameHeader(frame, payload.readableBytes(), SETTINGS, EMPTY, 0);
|
writeFrameHeader(frame, payload.readableBytes(), SETTINGS, new Http2Flags(), 0);
|
||||||
frame.writeBytes(payload);
|
frame.writeBytes(payload);
|
||||||
payload.release();
|
payload.release();
|
||||||
return frame;
|
return frame;
|
||||||
|
@ -15,258 +15,112 @@
|
|||||||
|
|
||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
import java.util.NoSuchElementException;
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_ENABLE_PUSH;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_HEADER_TABLE_SIZE;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_INITIAL_WINDOW_SIZE;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_MAX_CONCURRENT_STREAMS;
|
||||||
|
import io.netty.util.collection.IntObjectHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings for one endpoint in an HTTP/2 connection. Each of the values are optional as defined in
|
* Settings for one endpoint in an HTTP/2 connection. Each of the values are optional as defined in
|
||||||
* the spec for the SETTINGS frame.
|
* the spec for the SETTINGS frame. Permits storage of arbitrary key/value pairs but provides helper
|
||||||
|
* methods for standard settings.
|
||||||
*/
|
*/
|
||||||
public class Http2Settings {
|
public final class Http2Settings extends IntObjectHashMap<Long> {
|
||||||
private static final byte MAX_HEADER_TABLE_SIZE_MASK = 0x1;
|
|
||||||
private static final byte PUSH_ENABLED_MASK = 0x2;
|
|
||||||
private static final byte MAX_CONCURRENT_STREAMS_MASK = 0x4;
|
|
||||||
private static final byte INITIAL_WINDOW_SIZE_MASK = 0x8;
|
|
||||||
private static final byte ALLOW_COMPRESSION_MASK = 0x10;
|
|
||||||
|
|
||||||
private byte enabled;
|
public Http2Settings() {
|
||||||
private int maxHeaderTableSize;
|
|
||||||
private boolean pushEnabled;
|
|
||||||
private int maxConcurrentStreams;
|
|
||||||
private int initialWindowSize;
|
|
||||||
private boolean allowCompressedData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether or not the headerTableSize value is available.
|
|
||||||
*/
|
|
||||||
public boolean hasMaxHeaderTableSize() {
|
|
||||||
return isEnabled(MAX_HEADER_TABLE_SIZE_MASK);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Http2Settings(int initialCapacity, float loadFactor) {
|
||||||
* Gets the maximum HPACK header table size or throws {@link NoSuchElementException} if the
|
super(initialCapacity, loadFactor);
|
||||||
* value has not been set.
|
|
||||||
*/
|
|
||||||
public int maxHeaderTableSize() {
|
|
||||||
if (!hasMaxHeaderTableSize()) {
|
|
||||||
throw new NoSuchElementException("headerTableSize");
|
|
||||||
}
|
|
||||||
return maxHeaderTableSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Http2Settings(int initialCapacity) {
|
||||||
* Sets the maximum HPACK header table size to the specified value.
|
super(initialCapacity);
|
||||||
*/
|
|
||||||
public Http2Settings maxHeaderTableSize(int headerTableSize) {
|
|
||||||
if (headerTableSize < 0) {
|
|
||||||
throw new IllegalArgumentException("headerTableSize must be >= 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
enable(MAX_HEADER_TABLE_SIZE_MASK);
|
|
||||||
maxHeaderTableSize = headerTableSize;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether or not the pushEnabled value is available.
|
|
||||||
*/
|
|
||||||
public boolean hasPushEnabled() {
|
|
||||||
return isEnabled(PUSH_ENABLED_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets whether or not server push is enabled or throws {@link NoSuchElementException} if the
|
|
||||||
* value has not been set.
|
|
||||||
*/
|
|
||||||
public boolean pushEnabled() {
|
|
||||||
if (!hasPushEnabled()) {
|
|
||||||
throw new NoSuchElementException("pushEnabled");
|
|
||||||
}
|
|
||||||
return pushEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether or not server push is enabled.
|
|
||||||
*/
|
|
||||||
public Http2Settings pushEnabled(boolean pushEnabled) {
|
|
||||||
enable(PUSH_ENABLED_MASK);
|
|
||||||
this.pushEnabled = pushEnabled;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether or not the maxConcurrentStreams value is available.
|
|
||||||
*/
|
|
||||||
public boolean hasMaxConcurrentStreams() {
|
|
||||||
return isEnabled(MAX_CONCURRENT_STREAMS_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the maximum allowed concurrent streams or throws {@link NoSuchElementException} if the
|
|
||||||
* value has not been set.
|
|
||||||
*/
|
|
||||||
public int maxConcurrentStreams() {
|
|
||||||
if (!hasMaxConcurrentStreams()) {
|
|
||||||
throw new NoSuchElementException("maxConcurrentStreams");
|
|
||||||
}
|
|
||||||
return maxConcurrentStreams;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the maximum allowed concurrent streams to the specified value.
|
|
||||||
*/
|
|
||||||
public Http2Settings maxConcurrentStreams(int maxConcurrentStreams) {
|
|
||||||
if (maxConcurrentStreams < 0) {
|
|
||||||
throw new IllegalArgumentException("maxConcurrentStreams must be >= 0");
|
|
||||||
}
|
|
||||||
enable(MAX_CONCURRENT_STREAMS_MASK);
|
|
||||||
this.maxConcurrentStreams = maxConcurrentStreams;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether or not the initialWindowSize value is available.
|
|
||||||
*/
|
|
||||||
public boolean hasInitialWindowSize() {
|
|
||||||
return isEnabled(INITIAL_WINDOW_SIZE_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the initial flow control window size or throws {@link NoSuchElementException} if the
|
|
||||||
* value has not been set.
|
|
||||||
*/
|
|
||||||
public int initialWindowSize() {
|
|
||||||
if (!hasInitialWindowSize()) {
|
|
||||||
throw new NoSuchElementException("initialWindowSize");
|
|
||||||
}
|
|
||||||
return initialWindowSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the initial flow control window size to the specified value.
|
|
||||||
*/
|
|
||||||
public Http2Settings initialWindowSize(int initialWindowSize) {
|
|
||||||
if (initialWindowSize < 0) {
|
|
||||||
throw new IllegalArgumentException("initialWindowSize must be >= 0");
|
|
||||||
}
|
|
||||||
enable(INITIAL_WINDOW_SIZE_MASK);
|
|
||||||
this.initialWindowSize = initialWindowSize;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether or not the allowCompressedData value is available.
|
|
||||||
*/
|
|
||||||
public boolean hasAllowCompressedData() {
|
|
||||||
return isEnabled(ALLOW_COMPRESSION_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets whether the endpoint allows compressed data or throws {@link NoSuchElementException} if
|
|
||||||
* the value has not been set.
|
|
||||||
*/
|
|
||||||
public boolean allowCompressedData() {
|
|
||||||
if (!hasAllowCompressedData()) {
|
|
||||||
throw new NoSuchElementException("allowCompressedData");
|
|
||||||
}
|
|
||||||
return allowCompressedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether or not the endpoing allows compressed data.
|
|
||||||
*/
|
|
||||||
public Http2Settings allowCompressedData(boolean allowCompressedData) {
|
|
||||||
enable(ALLOW_COMPRESSION_MASK);
|
|
||||||
this.allowCompressedData = allowCompressedData;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites this settings object with the values of the given settings.
|
|
||||||
*
|
|
||||||
* @param source the source that will overwrite the current settings.
|
|
||||||
* @return this object.
|
|
||||||
*/
|
|
||||||
public Http2Settings copy(Http2Settings source) {
|
|
||||||
enabled = source.enabled;
|
|
||||||
allowCompressedData = source.allowCompressedData;
|
|
||||||
initialWindowSize = source.initialWindowSize;
|
|
||||||
maxConcurrentStreams = source.maxConcurrentStreams;
|
|
||||||
maxHeaderTableSize = source.maxHeaderTableSize;
|
|
||||||
pushEnabled = source.pushEnabled;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public Long put(int key, Long value) {
|
||||||
final int prime = 31;
|
verifyStandardSetting(key, value);
|
||||||
int result = 1;
|
return super.put(key, value);
|
||||||
result = prime * result + (allowCompressedData ? 1231 : 1237);
|
|
||||||
result = prime * result + enabled;
|
|
||||||
result = prime * result + maxHeaderTableSize;
|
|
||||||
result = prime * result + initialWindowSize;
|
|
||||||
result = prime * result + maxConcurrentStreams;
|
|
||||||
result = prime * result + (pushEnabled ? 1231 : 1237);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Long headerTableSize() {
|
||||||
public boolean equals(Object obj) {
|
return get(SETTINGS_HEADER_TABLE_SIZE);
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Http2Settings other = (Http2Settings) obj;
|
|
||||||
if (allowCompressedData != other.allowCompressedData) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (enabled != other.enabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (maxHeaderTableSize != other.maxHeaderTableSize) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (initialWindowSize != other.initialWindowSize) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (maxConcurrentStreams != other.maxConcurrentStreams) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pushEnabled == other.pushEnabled;
|
public Http2Settings headerTableSize(long value) {
|
||||||
|
put(SETTINGS_HEADER_TABLE_SIZE, value);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Boolean pushEnabled() {
|
||||||
public String toString() {
|
Long value = get(SETTINGS_ENABLE_PUSH);
|
||||||
StringBuilder builder = new StringBuilder("Http2Settings [");
|
if (value == null) {
|
||||||
if (hasMaxHeaderTableSize()) {
|
return null;
|
||||||
builder.append("maxHeaderTableSize=").append(maxHeaderTableSize).append(',');
|
|
||||||
}
|
}
|
||||||
if (hasPushEnabled()) {
|
return value != 0L;
|
||||||
builder.append("pushEnabled=").append(pushEnabled).append(',');
|
|
||||||
}
|
|
||||||
if (hasMaxConcurrentStreams()) {
|
|
||||||
builder.append("maxConcurrentStreams=").append(maxConcurrentStreams).append(',');
|
|
||||||
}
|
|
||||||
if (hasInitialWindowSize()) {
|
|
||||||
builder.append("initialWindowSize=").append(initialWindowSize).append(',');
|
|
||||||
}
|
|
||||||
if (hasAllowCompressedData()) {
|
|
||||||
builder.append("allowCompressedData=").append(allowCompressedData).append(',');
|
|
||||||
}
|
|
||||||
builder.append(']');
|
|
||||||
return builder.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enable(int mask) {
|
public Http2Settings pushEnabled(boolean enabled) {
|
||||||
enabled |= mask;
|
put(SETTINGS_ENABLE_PUSH, enabled? 1L : 0L);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isEnabled(int mask) {
|
public Long maxConcurrentStreams() {
|
||||||
return (enabled & mask) > 0;
|
return get(SETTINGS_MAX_CONCURRENT_STREAMS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Http2Settings maxConcurrentStreams(long value) {
|
||||||
|
put(SETTINGS_MAX_CONCURRENT_STREAMS, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer initialWindowSize() {
|
||||||
|
Long value = get(SETTINGS_INITIAL_WINDOW_SIZE);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value.intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Http2Settings initialWindowSize(int value) {
|
||||||
|
put(SETTINGS_INITIAL_WINDOW_SIZE, (long) value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Http2Settings copyFrom(Http2Settings settings) {
|
||||||
|
clear();
|
||||||
|
putAll(settings);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyStandardSetting(int key, Long value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new NullPointerException("value");
|
||||||
|
}
|
||||||
|
switch (key) {
|
||||||
|
case SETTINGS_HEADER_TABLE_SIZE:
|
||||||
|
if (value < 0L || value > MAX_UNSIGNED_INT) {
|
||||||
|
throw new IllegalArgumentException("Setting HEADER_TABLE_SIZE is invalid: " + value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SETTINGS_ENABLE_PUSH:
|
||||||
|
if (value != 0L && value != 1L) {
|
||||||
|
throw new IllegalArgumentException("Setting ENABLE_PUSH is invalid: " + value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SETTINGS_MAX_CONCURRENT_STREAMS:
|
||||||
|
if (value < 0L || value > MAX_UNSIGNED_INT) {
|
||||||
|
throw new IllegalArgumentException("Setting MAX_CONCURRENT_STREAMS is invalid: " + value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SETTINGS_INITIAL_WINDOW_SIZE:
|
||||||
|
if (value < 0L || value > Integer.MAX_VALUE) {
|
||||||
|
throw new IllegalArgumentException("Setting INITIAL_WINDOW_SIZE is invalid: " + value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,8 +38,8 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
public void setup() {
|
public void setup() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
server = new DefaultHttp2Connection(true, false);
|
server = new DefaultHttp2Connection(true);
|
||||||
client = new DefaultHttp2Connection(false, false);
|
client = new DefaultHttp2Connection(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = Http2Exception.class)
|
@Test(expected = Http2Exception.class)
|
||||||
|
@ -15,6 +15,10 @@
|
|||||||
|
|
||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
@ -23,15 +27,13 @@ import io.netty.channel.ChannelHandlerContext;
|
|||||||
import io.netty.channel.ChannelPromise;
|
import io.netty.channel.ChannelPromise;
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
import io.netty.util.ReferenceCountUtil;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for {@link DefaultHttp2FrameReader} and {@link DefaultHttp2FrameWriter}.
|
* Integration tests for {@link DefaultHttp2FrameReader} and {@link DefaultHttp2FrameWriter}.
|
||||||
*/
|
*/
|
||||||
@ -65,33 +67,33 @@ public class DefaultHttp2FrameIOTest {
|
|||||||
@Test
|
@Test
|
||||||
public void emptyDataShouldRoundtrip() throws Exception {
|
public void emptyDataShouldRoundtrip() throws Exception {
|
||||||
ByteBuf data = Unpooled.EMPTY_BUFFER;
|
ByteBuf data = Unpooled.EMPTY_BUFFER;
|
||||||
writer.writeData(ctx, promise, 1000, data, 0, false, false, false);
|
writer.writeData(ctx, promise, 1000, data, 0, false, false);
|
||||||
|
|
||||||
ByteBuf frame = captureWrite();
|
ByteBuf frame = captureWrite();
|
||||||
reader.readFrame(ctx, frame, observer);
|
reader.readFrame(ctx, frame, observer);
|
||||||
verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false), eq(false));
|
verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false));
|
||||||
frame.release();
|
frame.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dataShouldRoundtrip() throws Exception {
|
public void dataShouldRoundtrip() throws Exception {
|
||||||
ByteBuf data = dummyData();
|
ByteBuf data = dummyData();
|
||||||
writer.writeData(ctx, promise, 1000, data.retain().duplicate(), 0, false, false, false);
|
writer.writeData(ctx, promise, 1000, data.retain().duplicate(), 0, false, false);
|
||||||
|
|
||||||
ByteBuf frame = captureWrite();
|
ByteBuf frame = captureWrite();
|
||||||
reader.readFrame(ctx, frame, observer);
|
reader.readFrame(ctx, frame, observer);
|
||||||
verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false), eq(false));
|
verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false));
|
||||||
frame.release();
|
frame.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dataWithPaddingShouldRoundtrip() throws Exception {
|
public void dataWithPaddingShouldRoundtrip() throws Exception {
|
||||||
ByteBuf data = dummyData();
|
ByteBuf data = dummyData();
|
||||||
writer.writeData(ctx, promise, 1, data.retain().duplicate(), 256, true, true, true);
|
writer.writeData(ctx, promise, 1, data.retain().duplicate(), 0xFF, true, true);
|
||||||
|
|
||||||
ByteBuf frame = captureWrite();
|
ByteBuf frame = captureWrite();
|
||||||
reader.readFrame(ctx, frame, observer);
|
reader.readFrame(ctx, frame, observer);
|
||||||
verify(observer).onDataRead(eq(ctx), eq(1), eq(data), eq(256), eq(true), eq(true), eq(true));
|
verify(observer).onDataRead(eq(ctx), eq(1), eq(data), eq(0xFF), eq(true), eq(true));
|
||||||
frame.release();
|
frame.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,10 +131,9 @@ public class DefaultHttp2FrameIOTest {
|
|||||||
public void settingsShouldStripShouldRoundtrip() throws Exception {
|
public void settingsShouldStripShouldRoundtrip() throws Exception {
|
||||||
Http2Settings settings = new Http2Settings();
|
Http2Settings settings = new Http2Settings();
|
||||||
settings.pushEnabled(true);
|
settings.pushEnabled(true);
|
||||||
settings.maxHeaderTableSize(4096);
|
settings.headerTableSize(4096);
|
||||||
settings.initialWindowSize(123);
|
settings.initialWindowSize(123);
|
||||||
settings.maxConcurrentStreams(456);
|
settings.maxConcurrentStreams(456);
|
||||||
settings.allowCompressedData(false);
|
|
||||||
|
|
||||||
writer.writeSettings(ctx, promise, settings);
|
writer.writeSettings(ctx, promise, settings);
|
||||||
|
|
||||||
@ -193,35 +194,6 @@ public class DefaultHttp2FrameIOTest {
|
|||||||
frame.release();
|
frame.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void altSvcShouldRoundtrip() throws Exception {
|
|
||||||
writer.writeAltSvc(ctx, promise, 1, MAX_UNSIGNED_INT, MAX_UNSIGNED_SHORT, dummyData(), "host", "origin");
|
|
||||||
ByteBuf frame = captureWrite();
|
|
||||||
reader.readFrame(ctx, frame, observer);
|
|
||||||
verify(observer).onAltSvcRead(eq(ctx), eq(1), eq(MAX_UNSIGNED_INT), eq(MAX_UNSIGNED_SHORT),
|
|
||||||
eq(dummyData()), eq("host"), eq("origin"));
|
|
||||||
frame.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void altSvcWithoutOriginShouldRoundtrip() throws Exception {
|
|
||||||
writer.writeAltSvc(ctx, promise, 1, MAX_UNSIGNED_INT, MAX_UNSIGNED_SHORT, dummyData(), "host", null);
|
|
||||||
ByteBuf frame = captureWrite();
|
|
||||||
reader.readFrame(ctx, frame, observer);
|
|
||||||
verify(observer).onAltSvcRead(eq(ctx), eq(1), eq(MAX_UNSIGNED_INT), eq(MAX_UNSIGNED_SHORT),
|
|
||||||
eq(dummyData()), eq("host"), isNull(String.class));
|
|
||||||
frame.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void blockedShouldRoundtrip() throws Exception {
|
|
||||||
writer.writeBlocked(ctx, promise, 1);
|
|
||||||
ByteBuf frame = captureWrite();
|
|
||||||
reader.readFrame(ctx, frame, observer);
|
|
||||||
verify(observer).onBlockedRead(eq(ctx), eq(1));
|
|
||||||
frame.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void emptyHeadersShouldRoundtrip() throws Exception {
|
public void emptyHeadersShouldRoundtrip() throws Exception {
|
||||||
Http2Headers headers = Http2Headers.EMPTY_HEADERS;
|
Http2Headers headers = Http2Headers.EMPTY_HEADERS;
|
||||||
@ -235,10 +207,10 @@ public class DefaultHttp2FrameIOTest {
|
|||||||
@Test
|
@Test
|
||||||
public void emptyHeadersWithPaddingShouldRoundtrip() throws Exception {
|
public void emptyHeadersWithPaddingShouldRoundtrip() throws Exception {
|
||||||
Http2Headers headers = Http2Headers.EMPTY_HEADERS;
|
Http2Headers headers = Http2Headers.EMPTY_HEADERS;
|
||||||
writer.writeHeaders(ctx, promise, 1, headers, 256, true, true);
|
writer.writeHeaders(ctx, promise, 1, headers, 0xFF, true, true);
|
||||||
ByteBuf frame = captureWrite();
|
ByteBuf frame = captureWrite();
|
||||||
reader.readFrame(ctx, frame, observer);
|
reader.readFrame(ctx, frame, observer);
|
||||||
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(256), eq(true), eq(true));
|
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0xFF), eq(true), eq(true));
|
||||||
frame.release();
|
frame.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,10 +227,10 @@ public class DefaultHttp2FrameIOTest {
|
|||||||
@Test
|
@Test
|
||||||
public void headersWithPaddingWithoutPriorityShouldRoundtrip() throws Exception {
|
public void headersWithPaddingWithoutPriorityShouldRoundtrip() throws Exception {
|
||||||
Http2Headers headers = dummyHeaders();
|
Http2Headers headers = dummyHeaders();
|
||||||
writer.writeHeaders(ctx, promise, 1, headers, 256, true, true);
|
writer.writeHeaders(ctx, promise, 1, headers, 0xFF, true, true);
|
||||||
ByteBuf frame = captureWrite();
|
ByteBuf frame = captureWrite();
|
||||||
reader.readFrame(ctx, frame, observer);
|
reader.readFrame(ctx, frame, observer);
|
||||||
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(256), eq(true), eq(true));
|
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0xFF), eq(true), eq(true));
|
||||||
frame.release();
|
frame.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,10 +248,10 @@ public class DefaultHttp2FrameIOTest {
|
|||||||
@Test
|
@Test
|
||||||
public void headersWithPaddingWithPriorityShouldRoundtrip() throws Exception {
|
public void headersWithPaddingWithPriorityShouldRoundtrip() throws Exception {
|
||||||
Http2Headers headers = dummyHeaders();
|
Http2Headers headers = dummyHeaders();
|
||||||
writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 256, true, true);
|
writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0xFF, true, true);
|
||||||
ByteBuf frame = captureWrite();
|
ByteBuf frame = captureWrite();
|
||||||
reader.readFrame(ctx, frame, observer);
|
reader.readFrame(ctx, frame, observer);
|
||||||
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(256),
|
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0xFF),
|
||||||
eq(true), eq(true));
|
eq(true), eq(true));
|
||||||
frame.release();
|
frame.release();
|
||||||
}
|
}
|
||||||
@ -298,10 +270,10 @@ public class DefaultHttp2FrameIOTest {
|
|||||||
@Test
|
@Test
|
||||||
public void continuedHeadersWithPaddingShouldRoundtrip() throws Exception {
|
public void continuedHeadersWithPaddingShouldRoundtrip() throws Exception {
|
||||||
Http2Headers headers = largeHeaders();
|
Http2Headers headers = largeHeaders();
|
||||||
writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 256, true, true);
|
writer.writeHeaders(ctx, promise, 1, headers, 2, (short) 3, true, 0xFF, true, true);
|
||||||
ByteBuf frame = captureWrite();
|
ByteBuf frame = captureWrite();
|
||||||
reader.readFrame(ctx, frame, observer);
|
reader.readFrame(ctx, frame, observer);
|
||||||
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(256),
|
verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0xFF),
|
||||||
eq(true), eq(true));
|
eq(true), eq(true));
|
||||||
frame.release();
|
frame.release();
|
||||||
}
|
}
|
||||||
@ -329,10 +301,10 @@ public class DefaultHttp2FrameIOTest {
|
|||||||
@Test
|
@Test
|
||||||
public void pushPromiseWithPaddingShouldRoundtrip() throws Exception {
|
public void pushPromiseWithPaddingShouldRoundtrip() throws Exception {
|
||||||
Http2Headers headers = dummyHeaders();
|
Http2Headers headers = dummyHeaders();
|
||||||
writer.writePushPromise(ctx, promise, 1, 2, headers, 256);
|
writer.writePushPromise(ctx, promise, 1, 2, headers, 0xFF);
|
||||||
ByteBuf frame = captureWrite();
|
ByteBuf frame = captureWrite();
|
||||||
reader.readFrame(ctx, frame, observer);
|
reader.readFrame(ctx, frame, observer);
|
||||||
verify(observer).onPushPromiseRead(eq(ctx), eq(1), eq(2), eq(headers), eq(256));
|
verify(observer).onPushPromiseRead(eq(ctx), eq(1), eq(2), eq(headers), eq(0xFF));
|
||||||
frame.release();
|
frame.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,10 +321,10 @@ public class DefaultHttp2FrameIOTest {
|
|||||||
@Test
|
@Test
|
||||||
public void continuedPushPromiseWithPaddingShouldRoundtrip() throws Exception {
|
public void continuedPushPromiseWithPaddingShouldRoundtrip() throws Exception {
|
||||||
Http2Headers headers = largeHeaders();
|
Http2Headers headers = largeHeaders();
|
||||||
writer.writePushPromise(ctx, promise, 1, 2, headers, 256);
|
writer.writePushPromise(ctx, promise, 1, 2, headers, 0xFF);
|
||||||
ByteBuf frame = captureWrite();
|
ByteBuf frame = captureWrite();
|
||||||
reader.readFrame(ctx, frame, observer);
|
reader.readFrame(ctx, frame, observer);
|
||||||
verify(observer).onPushPromiseRead(eq(ctx), eq(1), eq(2), eq(headers), eq(256));
|
verify(observer).onPushPromiseRead(eq(ctx), eq(1), eq(2), eq(headers), eq(0xFF));
|
||||||
frame.release();
|
frame.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,17 +15,22 @@
|
|||||||
|
|
||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE;
|
||||||
|
import static org.mockito.Matchers.anyInt;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.handler.codec.http2.Http2InboundFlowController.FrameWriter;
|
import io.netty.handler.codec.http2.Http2InboundFlowController.FrameWriter;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link DefaultHttp2InboundFlowController}.
|
* Tests for {@link DefaultHttp2InboundFlowController}.
|
||||||
*/
|
*/
|
||||||
@ -46,7 +51,7 @@ public class DefaultHttp2InboundFlowControllerTest {
|
|||||||
public void setup() throws Http2Exception {
|
public void setup() throws Http2Exception {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
connection = new DefaultHttp2Connection(false, false);
|
connection = new DefaultHttp2Connection(false);
|
||||||
controller = new DefaultHttp2InboundFlowController(connection);
|
controller = new DefaultHttp2InboundFlowController(connection);
|
||||||
|
|
||||||
connection.local().createStream(STREAM_ID, false);
|
connection.local().createStream(STREAM_ID, false);
|
||||||
@ -60,14 +65,14 @@ public class DefaultHttp2InboundFlowControllerTest {
|
|||||||
|
|
||||||
@Test(expected = Http2Exception.class)
|
@Test(expected = Http2Exception.class)
|
||||||
public void connectionFlowControlExceededShouldThrow() throws Http2Exception {
|
public void connectionFlowControlExceededShouldThrow() throws Http2Exception {
|
||||||
applyFlowControl(DEFAULT_FLOW_CONTROL_WINDOW_SIZE + 1, true);
|
applyFlowControl(DEFAULT_WINDOW_SIZE + 1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void halfWindowRemainingShouldUpdateConnectionWindow() throws Http2Exception {
|
public void halfWindowRemainingShouldUpdateConnectionWindow() throws Http2Exception {
|
||||||
int dataSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE / 2 + 1;
|
int dataSize = DEFAULT_WINDOW_SIZE / 2 + 1;
|
||||||
int newWindow = DEFAULT_FLOW_CONTROL_WINDOW_SIZE - dataSize;
|
int newWindow = DEFAULT_WINDOW_SIZE - dataSize;
|
||||||
int windowDelta = DEFAULT_FLOW_CONTROL_WINDOW_SIZE - newWindow;
|
int windowDelta = DEFAULT_WINDOW_SIZE - newWindow;
|
||||||
|
|
||||||
// Set end-of-stream on the frame, so no window update will be sent for the stream.
|
// Set end-of-stream on the frame, so no window update will be sent for the stream.
|
||||||
applyFlowControl(dataSize, true);
|
applyFlowControl(dataSize, true);
|
||||||
@ -76,8 +81,8 @@ public class DefaultHttp2InboundFlowControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void halfWindowRemainingShouldUpdateAllWindows() throws Http2Exception {
|
public void halfWindowRemainingShouldUpdateAllWindows() throws Http2Exception {
|
||||||
int dataSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE / 2 + 1;
|
int dataSize = DEFAULT_WINDOW_SIZE / 2 + 1;
|
||||||
int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
int initialWindowSize = DEFAULT_WINDOW_SIZE;
|
||||||
int windowDelta = getWindowDelta(initialWindowSize, initialWindowSize, dataSize);
|
int windowDelta = getWindowDelta(initialWindowSize, initialWindowSize, dataSize);
|
||||||
|
|
||||||
// Don't set end-of-stream so we'll get a window update for the stream as well.
|
// Don't set end-of-stream so we'll get a window update for the stream as well.
|
||||||
@ -89,7 +94,7 @@ public class DefaultHttp2InboundFlowControllerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void initialWindowUpdateShouldAllowMoreFrames() throws Http2Exception {
|
public void initialWindowUpdateShouldAllowMoreFrames() throws Http2Exception {
|
||||||
// Send a frame that takes up the entire window.
|
// Send a frame that takes up the entire window.
|
||||||
int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
|
int initialWindowSize = DEFAULT_WINDOW_SIZE;
|
||||||
applyFlowControl(initialWindowSize, false);
|
applyFlowControl(initialWindowSize, false);
|
||||||
|
|
||||||
// Update the initial window size to allow another frame.
|
// Update the initial window size to allow another frame.
|
||||||
@ -113,8 +118,7 @@ public class DefaultHttp2InboundFlowControllerTest {
|
|||||||
|
|
||||||
private void applyFlowControl(int dataSize, boolean endOfStream) throws Http2Exception {
|
private void applyFlowControl(int dataSize, boolean endOfStream) throws Http2Exception {
|
||||||
ByteBuf buf = dummyData(dataSize);
|
ByteBuf buf = dummyData(dataSize);
|
||||||
controller.applyInboundFlowControl(STREAM_ID, buf, 0, endOfStream, false,
|
controller.applyInboundFlowControl(STREAM_ID, buf, 0, endOfStream, false, frameWriter);
|
||||||
false, frameWriter);
|
|
||||||
buf.release();
|
buf.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
|||||||
public void setup() throws Http2Exception {
|
public void setup() throws Http2Exception {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
connection = new DefaultHttp2Connection(false, false);
|
connection = new DefaultHttp2Connection(false);
|
||||||
controller = new DefaultHttp2OutboundFlowController(connection);
|
controller = new DefaultHttp2OutboundFlowController(connection);
|
||||||
|
|
||||||
connection.local().createStream(STREAM_A, false);
|
connection.local().createStream(STREAM_A, false);
|
||||||
@ -142,7 +142,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
|||||||
public void connectionWindowUpdateShouldSendFrame() throws Http2Exception {
|
public void connectionWindowUpdateShouldSendFrame() throws Http2Exception {
|
||||||
// Set the connection window size to zero.
|
// Set the connection window size to zero.
|
||||||
controller
|
controller
|
||||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
ByteBuf data = dummyData(10);
|
ByteBuf data = dummyData(10);
|
||||||
send(STREAM_A, data);
|
send(STREAM_A, data);
|
||||||
@ -162,7 +162,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
|||||||
public void connectionWindowUpdateShouldSendPartialFrame() throws Http2Exception {
|
public void connectionWindowUpdateShouldSendPartialFrame() throws Http2Exception {
|
||||||
// Set the connection window size to zero.
|
// Set the connection window size to zero.
|
||||||
controller
|
controller
|
||||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
ByteBuf data = dummyData(10);
|
ByteBuf data = dummyData(10);
|
||||||
send(STREAM_A, data);
|
send(STREAM_A, data);
|
||||||
@ -183,7 +183,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void streamWindowUpdateShouldSendFrame() throws Http2Exception {
|
public void streamWindowUpdateShouldSendFrame() throws Http2Exception {
|
||||||
// Set the stream window size to zero.
|
// Set the stream window size to zero.
|
||||||
controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
ByteBuf data = dummyData(10);
|
ByteBuf data = dummyData(10);
|
||||||
send(STREAM_A, data);
|
send(STREAM_A, data);
|
||||||
@ -202,7 +202,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void streamWindowUpdateShouldSendPartialFrame() throws Http2Exception {
|
public void streamWindowUpdateShouldSendPartialFrame() throws Http2Exception {
|
||||||
// Set the stream window size to zero.
|
// Set the stream window size to zero.
|
||||||
controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
ByteBuf data = dummyData(10);
|
ByteBuf data = dummyData(10);
|
||||||
send(STREAM_A, data);
|
send(STREAM_A, data);
|
||||||
@ -235,10 +235,10 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
|||||||
public void blockedStreamShouldSpreadDataToChildren() throws Http2Exception {
|
public void blockedStreamShouldSpreadDataToChildren() throws Http2Exception {
|
||||||
// Block the connection
|
// Block the connection
|
||||||
controller
|
controller
|
||||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
// Block stream A
|
// Block stream A
|
||||||
controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
controller.updateOutboundWindowSize(STREAM_A, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
// Try sending 10 bytes on each stream. They will be pending until we free up the
|
// Try sending 10 bytes on each stream. They will be pending until we free up the
|
||||||
// connection.
|
// connection.
|
||||||
@ -287,10 +287,10 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
|||||||
public void childrenShouldNotSendDataUntilParentBlocked() throws Http2Exception {
|
public void childrenShouldNotSendDataUntilParentBlocked() throws Http2Exception {
|
||||||
// Block the connection
|
// Block the connection
|
||||||
controller
|
controller
|
||||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
// Block stream B
|
// Block stream B
|
||||||
controller.updateOutboundWindowSize(STREAM_B, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
controller.updateOutboundWindowSize(STREAM_B, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
// Send 10 bytes to each.
|
// Send 10 bytes to each.
|
||||||
send(STREAM_A, dummyData(10));
|
send(STREAM_A, dummyData(10));
|
||||||
@ -330,10 +330,10 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
|||||||
public void parentShouldWaterFallDataToChildren() throws Http2Exception {
|
public void parentShouldWaterFallDataToChildren() throws Http2Exception {
|
||||||
// Block the connection
|
// Block the connection
|
||||||
controller
|
controller
|
||||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
// Block stream B
|
// Block stream B
|
||||||
controller.updateOutboundWindowSize(STREAM_B, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
controller.updateOutboundWindowSize(STREAM_B, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
// Only send 5 to A so that it will allow data from its children.
|
// Only send 5 to A so that it will allow data from its children.
|
||||||
send(STREAM_A, dummyData(5));
|
send(STREAM_A, dummyData(5));
|
||||||
@ -392,10 +392,10 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
|||||||
public void reprioritizeShouldAdjustOutboundFlow() throws Http2Exception {
|
public void reprioritizeShouldAdjustOutboundFlow() throws Http2Exception {
|
||||||
// Block the connection
|
// Block the connection
|
||||||
controller
|
controller
|
||||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
// Block stream B
|
// Block stream B
|
||||||
controller.updateOutboundWindowSize(STREAM_B, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
controller.updateOutboundWindowSize(STREAM_B, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
// Send 10 bytes to each.
|
// Send 10 bytes to each.
|
||||||
send(STREAM_A, dummyData(10));
|
send(STREAM_A, dummyData(10));
|
||||||
@ -437,7 +437,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
|||||||
public void writeShouldPreferHighestWeight() throws Http2Exception {
|
public void writeShouldPreferHighestWeight() throws Http2Exception {
|
||||||
// Block the connection
|
// Block the connection
|
||||||
controller
|
controller
|
||||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
// Root the streams at the connection and assign weights.
|
// Root the streams at the connection and assign weights.
|
||||||
setPriority(STREAM_A, 0, (short) 50, false);
|
setPriority(STREAM_A, 0, (short) 50, false);
|
||||||
@ -501,7 +501,7 @@ public class DefaultHttp2OutboundFlowControllerTest {
|
|||||||
public void samePriorityShouldWriteEqualData() throws Http2Exception {
|
public void samePriorityShouldWriteEqualData() throws Http2Exception {
|
||||||
// Block the connection
|
// Block the connection
|
||||||
controller
|
controller
|
||||||
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
|
.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
// Root the streams at the connection with the same weights.
|
// Root the streams at the connection with the same weights.
|
||||||
setPriority(STREAM_A, 0, DEFAULT_PRIORITY_WEIGHT, false);
|
setPriority(STREAM_A, 0, DEFAULT_PRIORITY_WEIGHT, false);
|
||||||
|
@ -15,6 +15,34 @@
|
|||||||
|
|
||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
|
import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
|
||||||
|
import static io.netty.buffer.Unpooled.copiedBuffer;
|
||||||
|
import static io.netty.buffer.Unpooled.wrappedBuffer;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf;
|
||||||
|
import static io.netty.handler.codec.http2.Http2CodecUtil.emptyPingBuf;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Error.NO_ERROR;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Headers.EMPTY_HEADERS;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_LOCAL;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Stream.State.OPEN;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_LOCAL;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_REMOTE;
|
||||||
|
import static io.netty.util.CharsetUtil.UTF_8;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Matchers.anyBoolean;
|
||||||
|
import static org.mockito.Matchers.anyInt;
|
||||||
|
import static org.mockito.Matchers.anyLong;
|
||||||
|
import static org.mockito.Matchers.anyShort;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.doNothing;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
@ -22,6 +50,9 @@ import io.netty.channel.ChannelFuture;
|
|||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.ChannelPromise;
|
import io.netty.channel.ChannelPromise;
|
||||||
import io.netty.channel.DefaultChannelPromise;
|
import io.netty.channel.DefaultChannelPromise;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -29,18 +60,6 @@ import org.mockito.ArgumentCaptor;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import static io.netty.buffer.Unpooled.*;
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Error.*;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Exception.*;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Headers.*;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Stream.State.*;
|
|
||||||
import static io.netty.util.CharsetUtil.*;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link DelegatingHttp2ConnectionHandlerTest} and its base class
|
* Tests for {@link DelegatingHttp2ConnectionHandlerTest} and its base class
|
||||||
* {@link AbstractHttp2ConnectionHandler}.
|
* {@link AbstractHttp2ConnectionHandler}.
|
||||||
@ -123,16 +142,14 @@ public class DelegatingHttp2ConnectionHandlerTest {
|
|||||||
|
|
||||||
// Simulate activation of the handler to force writing the initial settings.
|
// Simulate activation of the handler to force writing the initial settings.
|
||||||
Http2Settings settings = new Http2Settings();
|
Http2Settings settings = new Http2Settings();
|
||||||
settings.allowCompressedData(true);
|
|
||||||
settings.initialWindowSize(10);
|
settings.initialWindowSize(10);
|
||||||
settings.pushEnabled(true);
|
settings.pushEnabled(true);
|
||||||
settings.maxConcurrentStreams(100);
|
settings.maxConcurrentStreams(100);
|
||||||
settings.maxHeaderTableSize(200);
|
settings.headerTableSize(200);
|
||||||
when(local.allowCompressedData()).thenReturn(true);
|
|
||||||
when(inboundFlow.initialInboundWindowSize()).thenReturn(10);
|
when(inboundFlow.initialInboundWindowSize()).thenReturn(10);
|
||||||
when(local.allowPushTo()).thenReturn(true);
|
when(local.allowPushTo()).thenReturn(true);
|
||||||
when(remote.maxStreams()).thenReturn(100);
|
when(remote.maxStreams()).thenReturn(100);
|
||||||
when(reader.maxHeaderTableSize()).thenReturn(200);
|
when(reader.maxHeaderTableSize()).thenReturn(200L);
|
||||||
handler.handlerAdded(ctx);
|
handler.handlerAdded(ctx);
|
||||||
verify(writer).writeSettings(eq(ctx), eq(promise), eq(settings));
|
verify(writer).writeSettings(eq(ctx), eq(promise), eq(settings));
|
||||||
|
|
||||||
@ -229,40 +246,23 @@ public class DelegatingHttp2ConnectionHandlerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void dataReadAfterGoAwayShouldApplyFlowControl() throws Exception {
|
public void dataReadAfterGoAwayShouldApplyFlowControl() throws Exception {
|
||||||
when(remote.isGoAwayReceived()).thenReturn(true);
|
when(remote.isGoAwayReceived()).thenReturn(true);
|
||||||
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, true, true);
|
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, true);
|
||||||
verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10),
|
verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10),
|
||||||
eq(true), eq(true), eq(true), any(Http2InboundFlowController.FrameWriter.class));
|
eq(true), eq(true), any(Http2InboundFlowController.FrameWriter.class));
|
||||||
|
|
||||||
// Verify that the event was absorbed and not propagated to the oberver.
|
// Verify that the event was absorbed and not propagated to the oberver.
|
||||||
verify(observer, never()).onDataRead(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean(),
|
verify(observer, never()).onDataRead(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(),
|
||||||
anyBoolean(), anyBoolean());
|
anyBoolean(), anyBoolean());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dataReadWithEndOfStreamShouldCloseRemoteSide() throws Exception {
|
public void dataReadWithEndOfStreamShouldCloseRemoteSide() throws Exception {
|
||||||
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, false, false);
|
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, true, false);
|
||||||
verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10),
|
verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10),
|
||||||
eq(true), eq(false), eq(false), any(Http2InboundFlowController.FrameWriter.class));
|
eq(true), eq(false), any(Http2InboundFlowController.FrameWriter.class));
|
||||||
verify(stream).closeRemoteSide();
|
verify(stream).closeRemoteSide();
|
||||||
verify(observer).onDataRead(eq(ctx), eq(STREAM_ID), eq(dummyData()), eq(10), eq(true),
|
verify(observer).onDataRead(eq(ctx), eq(STREAM_ID), eq(dummyData()), eq(10), eq(true),
|
||||||
eq(false), eq(false));
|
eq(false));
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void dataReadWithShouldAllowCompression() throws Exception {
|
|
||||||
when(local.allowCompressedData()).thenReturn(true);
|
|
||||||
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, false, false, true);
|
|
||||||
verify(inboundFlow).applyInboundFlowControl(eq(STREAM_ID), eq(dummyData()), eq(10),
|
|
||||||
eq(false), eq(false), eq(true), any(Http2InboundFlowController.FrameWriter.class));
|
|
||||||
verify(stream, never()).closeRemoteSide();
|
|
||||||
verify(observer).onDataRead(eq(ctx), eq(STREAM_ID), eq(dummyData()), eq(10), eq(false),
|
|
||||||
eq(false), eq(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = Http2Exception.class)
|
|
||||||
public void dataReadShouldDisallowCompression() throws Exception {
|
|
||||||
when(local.allowCompressedData()).thenReturn(false);
|
|
||||||
decode().onDataRead(ctx, STREAM_ID, dummyData(), 10, false, false, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -421,14 +421,12 @@ public class DelegatingHttp2ConnectionHandlerTest {
|
|||||||
settings.pushEnabled(true);
|
settings.pushEnabled(true);
|
||||||
settings.initialWindowSize(123);
|
settings.initialWindowSize(123);
|
||||||
settings.maxConcurrentStreams(456);
|
settings.maxConcurrentStreams(456);
|
||||||
settings.allowCompressedData(true);
|
settings.headerTableSize(789);
|
||||||
settings.maxHeaderTableSize(789);
|
|
||||||
decode().onSettingsRead(ctx, settings);
|
decode().onSettingsRead(ctx, settings);
|
||||||
verify(remote).allowPushTo(true);
|
verify(remote).allowPushTo(true);
|
||||||
verify(outboundFlow).initialOutboundWindowSize(123);
|
verify(outboundFlow).initialOutboundWindowSize(123);
|
||||||
verify(local).maxStreams(456);
|
verify(local).maxStreams(456);
|
||||||
assertTrue(handler.settings().allowCompressedData());
|
verify(writer).maxHeaderTableSize(789L);
|
||||||
verify(writer).maxHeaderTableSize(789);
|
|
||||||
// Take into account the time this was called during setup().
|
// Take into account the time this was called during setup().
|
||||||
verify(writer, times(2)).writeSettingsAck(eq(ctx), eq(promise));
|
verify(writer, times(2)).writeSettingsAck(eq(ctx), eq(promise));
|
||||||
verify(observer).onSettingsRead(eq(ctx), eq(settings));
|
verify(observer).onSettingsRead(eq(ctx), eq(settings));
|
||||||
@ -441,22 +439,6 @@ public class DelegatingHttp2ConnectionHandlerTest {
|
|||||||
verify(observer).onGoAwayRead(eq(ctx), eq(1), eq(2L), eq(EMPTY_BUFFER));
|
verify(observer).onGoAwayRead(eq(ctx), eq(1), eq(2L), eq(EMPTY_BUFFER));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = Http2Exception.class)
|
|
||||||
public void serverAltSvcReadShouldThrow() throws Exception {
|
|
||||||
when(connection.isServer()).thenReturn(true);
|
|
||||||
decode().onAltSvcRead(ctx, STREAM_ID, 1, 2, EMPTY_BUFFER, "www.example.com", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void clientAltSvcReadShouldNotifyObserver() throws Exception {
|
|
||||||
String host = "www.host.com";
|
|
||||||
String origin = "www.origin.com";
|
|
||||||
when(connection.isServer()).thenReturn(false);
|
|
||||||
decode().onAltSvcRead(ctx, STREAM_ID, 1, 2, EMPTY_BUFFER, host, origin);
|
|
||||||
verify(observer).onAltSvcRead(eq(ctx), eq(STREAM_ID), eq(1L), eq(2), eq(EMPTY_BUFFER),
|
|
||||||
eq(host), eq(origin));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dataWriteAfterGoAwayShouldFail() throws Exception {
|
public void dataWriteAfterGoAwayShouldFail() throws Exception {
|
||||||
when(connection.isGoAway()).thenReturn(true);
|
when(connection.isGoAway()).thenReturn(true);
|
||||||
@ -464,21 +446,6 @@ public class DelegatingHttp2ConnectionHandlerTest {
|
|||||||
assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception);
|
assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void dataWriteShouldDisallowCompression() throws Exception {
|
|
||||||
when(local.allowCompressedData()).thenReturn(false);
|
|
||||||
ChannelFuture future = handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false, true);
|
|
||||||
assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void dataWriteShouldAllowCompression() throws Exception {
|
|
||||||
when(remote.allowCompressedData()).thenReturn(true);
|
|
||||||
handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false, true);
|
|
||||||
verify(outboundFlow).sendFlowControlled(eq(STREAM_ID), eq(dummyData()), eq(0), eq(false),
|
|
||||||
eq(false), eq(true), any(Http2OutboundFlowController.FrameWriter.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dataWriteShouldSucceed() throws Exception {
|
public void dataWriteShouldSucceed() throws Exception {
|
||||||
handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false, false);
|
handler.writeData(ctx, promise, STREAM_ID, dummyData(), 0, false, false, false);
|
||||||
@ -600,44 +567,23 @@ public class DelegatingHttp2ConnectionHandlerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void settingsWriteShouldNotUpdateSettings() throws Exception {
|
public void settingsWriteShouldNotUpdateSettings() throws Exception {
|
||||||
Http2Settings settings = new Http2Settings();
|
Http2Settings settings = new Http2Settings();
|
||||||
settings.allowCompressedData(false);
|
|
||||||
settings.initialWindowSize(100);
|
settings.initialWindowSize(100);
|
||||||
settings.pushEnabled(false);
|
settings.pushEnabled(false);
|
||||||
settings.maxConcurrentStreams(1000);
|
settings.maxConcurrentStreams(1000);
|
||||||
settings.maxHeaderTableSize(2000);
|
settings.headerTableSize(2000);
|
||||||
handler.writeSettings(ctx, promise, settings);
|
handler.writeSettings(ctx, promise, settings);
|
||||||
verify(writer).writeSettings(eq(ctx), eq(promise), eq(settings));
|
verify(writer).writeSettings(eq(ctx), eq(promise), eq(settings));
|
||||||
// Verify that application of local settings must not be done when it is dispatched.
|
// Verify that application of local settings must not be done when it is dispatched.
|
||||||
verify(local, never()).allowCompressedData(eq(false));
|
|
||||||
verify(inboundFlow, never()).initialInboundWindowSize(eq(100));
|
verify(inboundFlow, never()).initialInboundWindowSize(eq(100));
|
||||||
verify(local, never()).allowPushTo(eq(false));
|
verify(local, never()).allowPushTo(eq(false));
|
||||||
verify(remote, never()).maxStreams(eq(1000));
|
verify(remote, never()).maxStreams(eq(1000));
|
||||||
verify(reader, never()).maxHeaderTableSize(eq(2000));
|
verify(reader, never()).maxHeaderTableSize(eq(2000L));
|
||||||
// Verify that settings values are applied on the reception of SETTINGS ACK
|
// Verify that settings values are applied on the reception of SETTINGS ACK
|
||||||
decode().onSettingsAckRead(ctx);
|
decode().onSettingsAckRead(ctx);
|
||||||
verify(local).allowCompressedData(eq(false));
|
|
||||||
verify(inboundFlow).initialInboundWindowSize(eq(100));
|
verify(inboundFlow).initialInboundWindowSize(eq(100));
|
||||||
verify(local).allowPushTo(eq(false));
|
verify(local).allowPushTo(eq(false));
|
||||||
verify(remote).maxStreams(eq(1000));
|
verify(remote).maxStreams(eq(1000));
|
||||||
verify(reader).maxHeaderTableSize(eq(2000));
|
verify(reader).maxHeaderTableSize(eq(2000L));
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void clientWriteAltSvcShouldThrow() throws Exception {
|
|
||||||
when(connection.isServer()).thenReturn(false);
|
|
||||||
ChannelFuture future = handler.writeAltSvc(ctx, promise, STREAM_ID, 1, 2, EMPTY_BUFFER,
|
|
||||||
"www.example.com", null);
|
|
||||||
assertTrue(future.awaitUninterruptibly().cause() instanceof Http2Exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void serverWriteAltSvcShouldSucceed() throws Exception {
|
|
||||||
String host = "www.host.com";
|
|
||||||
String origin = "www.origin.com";
|
|
||||||
when(connection.isServer()).thenReturn(true);
|
|
||||||
handler.writeAltSvc(ctx, promise, STREAM_ID, 1, 2, EMPTY_BUFFER, host, origin);
|
|
||||||
verify(writer).writeAltSvc(eq(ctx), eq(promise), eq(STREAM_ID), eq(1L), eq(2),
|
|
||||||
eq(EMPTY_BUFFER), eq(host), eq(origin));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ByteBuf dummyData() {
|
private static ByteBuf dummyData() {
|
||||||
|
@ -129,7 +129,7 @@ public class Http2ConnectionRoundtripTest {
|
|||||||
anyInt(), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false),
|
anyInt(), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false),
|
||||||
eq(false));
|
eq(false));
|
||||||
verify(serverObserver, times(NUM_STREAMS)).onDataRead(any(ChannelHandlerContext.class),
|
verify(serverObserver, times(NUM_STREAMS)).onDataRead(any(ChannelHandlerContext.class),
|
||||||
anyInt(), eq(Unpooled.copiedBuffer(text.getBytes())), eq(0), eq(true), eq(true), eq(false));
|
anyInt(), eq(Unpooled.copiedBuffer(text.getBytes())), eq(0), eq(true), eq(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void awaitRequests() throws Exception {
|
private void awaitRequests() throws Exception {
|
||||||
@ -152,10 +152,10 @@ public class Http2ConnectionRoundtripTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||||
boolean endOfStream, boolean endOfSegment, boolean compressed)
|
boolean endOfStream, boolean endOfSegment)
|
||||||
throws Http2Exception {
|
throws Http2Exception {
|
||||||
serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream,
|
serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream,
|
||||||
endOfSegment, compressed);
|
endOfSegment);
|
||||||
requestLatch.countDown();
|
requestLatch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,16 +235,9 @@ public class Http2ConnectionRoundtripTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
|
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
|
||||||
ByteBuf protocolId, String host, String origin) throws Http2Exception {
|
Http2Flags flags, ByteBuf payload) {
|
||||||
serverObserver
|
serverObserver.onUnknownFrame(ctx, frameType, streamId, flags, payload);
|
||||||
.onAltSvcRead(ctx, streamId, maxAge, port, copy(protocolId), host, origin);
|
|
||||||
requestLatch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
|
|
||||||
serverObserver.onBlockedRead(ctx, streamId);
|
|
||||||
requestLatch.countDown();
|
requestLatch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,12 +116,12 @@ public class Http2FrameRoundtripTest {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
frameWriter.writeData(ctx(), newPromise(), 0x7FFFFFFF,
|
frameWriter.writeData(ctx(), newPromise(), 0x7FFFFFFF,
|
||||||
Unpooled.copiedBuffer(text.getBytes()), 100, true, false, false);
|
Unpooled.copiedBuffer(text.getBytes()), 100, true, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
awaitRequests();
|
awaitRequests();
|
||||||
verify(serverObserver).onDataRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
|
verify(serverObserver).onDataRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
|
||||||
dataCaptor.capture(), eq(100), eq(true), eq(false), eq(false));
|
dataCaptor.capture(), eq(100), eq(true), eq(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -231,10 +231,9 @@ public class Http2FrameRoundtripTest {
|
|||||||
@Test
|
@Test
|
||||||
public void settingsFrameShouldMatch() throws Exception {
|
public void settingsFrameShouldMatch() throws Exception {
|
||||||
final Http2Settings settings = new Http2Settings();
|
final Http2Settings settings = new Http2Settings();
|
||||||
settings.allowCompressedData(true);
|
|
||||||
settings.initialWindowSize(10);
|
settings.initialWindowSize(10);
|
||||||
settings.maxConcurrentStreams(1000);
|
settings.maxConcurrentStreams(1000);
|
||||||
settings.maxHeaderTableSize(4096);
|
settings.headerTableSize(4096);
|
||||||
runInChannel(clientChannel, new Http2Runnable() {
|
runInChannel(clientChannel, new Http2Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@ -274,7 +273,7 @@ public class Http2FrameRoundtripTest {
|
|||||||
frameWriter.writeHeaders(ctx(), newPromise(), i, headers, 0, (short) 16, false,
|
frameWriter.writeHeaders(ctx(), newPromise(), i, headers, 0, (short) 16, false,
|
||||||
0, false, false);
|
0, false, false);
|
||||||
frameWriter.writeData(ctx(), newPromise(), i,
|
frameWriter.writeData(ctx(), newPromise(), i,
|
||||||
Unpooled.copiedBuffer(text.getBytes()), 0, true, true, false);
|
Unpooled.copiedBuffer(text.getBytes()), 0, true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -310,10 +309,10 @@ public class Http2FrameRoundtripTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data,
|
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data,
|
||||||
int padding, boolean endOfStream, boolean endOfSegment, boolean compressed)
|
int padding, boolean endOfStream, boolean endOfSegment)
|
||||||
throws Http2Exception {
|
throws Http2Exception {
|
||||||
observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream,
|
observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream,
|
||||||
endOfSegment, compressed);
|
endOfSegment);
|
||||||
requestLatch.countDown();
|
requestLatch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,18 +399,9 @@ public class Http2FrameRoundtripTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge,
|
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
|
||||||
int port, ByteBuf protocolId, String host, String origin)
|
Http2Flags flags, ByteBuf payload) {
|
||||||
throws Http2Exception {
|
observer.onUnknownFrame(ctx, frameType, streamId, flags, payload);
|
||||||
observer.onAltSvcRead(ctx, streamId, maxAge, port, copy(protocolId), host,
|
|
||||||
origin);
|
|
||||||
requestLatch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlockedRead(ChannelHandlerContext ctx, int streamId)
|
|
||||||
throws Http2Exception {
|
|
||||||
observer.onBlockedRead(ctx, streamId);
|
|
||||||
requestLatch.countDown();
|
requestLatch.countDown();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -16,11 +16,9 @@
|
|||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@ -37,105 +35,23 @@ public class Http2SettingsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void allValuesShouldBeNotSet() {
|
public void standardSettingsShouldBeNotSet() {
|
||||||
assertFalse(settings.hasAllowCompressedData());
|
assertEquals(0, settings.size());
|
||||||
assertFalse(settings.hasMaxHeaderTableSize());
|
assertNull(settings.headerTableSize());
|
||||||
assertFalse(settings.hasInitialWindowSize());
|
assertNull(settings.initialWindowSize());
|
||||||
assertFalse(settings.hasMaxConcurrentStreams());
|
assertNull(settings.maxConcurrentStreams());
|
||||||
assertFalse(settings.hasPushEnabled());
|
assertNull(settings.pushEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = NoSuchElementException.class)
|
@Test
|
||||||
public void unsetAllowCompressedDataShouldThrow() {
|
public void standardSettingsShouldBeSet() {
|
||||||
// Set everything else.
|
|
||||||
settings.maxHeaderTableSize(1);
|
|
||||||
settings.initialWindowSize(1);
|
settings.initialWindowSize(1);
|
||||||
settings.maxConcurrentStreams(1);
|
settings.maxConcurrentStreams(2);
|
||||||
settings.pushEnabled(true);
|
settings.pushEnabled(true);
|
||||||
|
settings.headerTableSize(3);
|
||||||
settings.allowCompressedData();
|
assertEquals(1, (int) settings.initialWindowSize());
|
||||||
}
|
assertEquals(2L, (long) settings.maxConcurrentStreams());
|
||||||
|
|
||||||
@Test(expected = NoSuchElementException.class)
|
|
||||||
public void unsetHeaderTableSizeShouldThrow() {
|
|
||||||
// Set everything else.
|
|
||||||
settings.allowCompressedData(true);
|
|
||||||
settings.initialWindowSize(1);
|
|
||||||
settings.maxConcurrentStreams(1);
|
|
||||||
settings.pushEnabled(true);
|
|
||||||
|
|
||||||
settings.maxHeaderTableSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = NoSuchElementException.class)
|
|
||||||
public void unsetInitialWindowSizeShouldThrow() {
|
|
||||||
// Set everything else.
|
|
||||||
settings.allowCompressedData(true);
|
|
||||||
settings.maxHeaderTableSize(1);
|
|
||||||
settings.maxConcurrentStreams(1);
|
|
||||||
settings.pushEnabled(true);
|
|
||||||
|
|
||||||
settings.initialWindowSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = NoSuchElementException.class)
|
|
||||||
public void unsetMaxConcurrentStreamsShouldThrow() {
|
|
||||||
// Set everything else.
|
|
||||||
settings.allowCompressedData(true);
|
|
||||||
settings.maxHeaderTableSize(1);
|
|
||||||
settings.initialWindowSize(1);
|
|
||||||
settings.pushEnabled(true);
|
|
||||||
|
|
||||||
settings.maxConcurrentStreams();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = NoSuchElementException.class)
|
|
||||||
public void unsetPushEnabledShouldThrow() {
|
|
||||||
// Set everything else.
|
|
||||||
settings.allowCompressedData(true);
|
|
||||||
settings.maxHeaderTableSize(1);
|
|
||||||
settings.initialWindowSize(1);
|
|
||||||
settings.maxConcurrentStreams(1);
|
|
||||||
|
|
||||||
settings.pushEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void allowCompressedDataShouldBeSet() {
|
|
||||||
settings.allowCompressedData(true);
|
|
||||||
assertTrue(settings.hasAllowCompressedData());
|
|
||||||
assertTrue(settings.allowCompressedData());
|
|
||||||
settings.allowCompressedData(false);
|
|
||||||
assertFalse(settings.allowCompressedData());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void headerTableSizeShouldBeSet() {
|
|
||||||
settings.maxHeaderTableSize(123);
|
|
||||||
assertTrue(settings.hasMaxHeaderTableSize());
|
|
||||||
assertEquals(123, settings.maxHeaderTableSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void initialWindowSizeShouldBeSet() {
|
|
||||||
settings.initialWindowSize(123);
|
|
||||||
assertTrue(settings.hasInitialWindowSize());
|
|
||||||
assertEquals(123, settings.initialWindowSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void maxConcurrentStreamsShouldBeSet() {
|
|
||||||
settings.maxConcurrentStreams(123);
|
|
||||||
assertTrue(settings.hasMaxConcurrentStreams());
|
|
||||||
assertEquals(123, settings.maxConcurrentStreams());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void pushEnabledShouldBeSet() {
|
|
||||||
settings.pushEnabled(true);
|
|
||||||
assertTrue(settings.hasPushEnabled());
|
|
||||||
assertTrue(settings.pushEnabled());
|
assertTrue(settings.pushEnabled());
|
||||||
settings.pushEnabled(false);
|
assertEquals(3L, (long) settings.headerTableSize());
|
||||||
assertFalse(settings.pushEnabled());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,6 +247,42 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
|
|||||||
return outValues;
|
return outValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
for (Entry<V> entry : entries()) {
|
||||||
|
V value = entry.value();
|
||||||
|
int hash = value == null ? 0 : value.hashCode();
|
||||||
|
result = prime * result + hash;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj || obj == null || getClass() != obj.getClass()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
IntObjectHashMap other = (IntObjectHashMap) obj;
|
||||||
|
if (size != other.size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (Entry<V> entry : entries()) {
|
||||||
|
V value = entry.value();
|
||||||
|
Object otherValue = other.get(entry.key());
|
||||||
|
if (value == null) {
|
||||||
|
if (otherValue != null || !other.containsKey(entry.key())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!value.equals(otherValue)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies the occupied entries from the source to the target array.
|
* Copies the occupied entries from the source to the target array.
|
||||||
*/
|
*/
|
||||||
|
@ -57,7 +57,7 @@ public class Http2ClientConnectionHandler extends AbstractHttp2ConnectionHandler
|
|||||||
private ByteBuf collectedData;
|
private ByteBuf collectedData;
|
||||||
|
|
||||||
public Http2ClientConnectionHandler(ChannelPromise initPromise, ChannelPromise responsePromise) {
|
public Http2ClientConnectionHandler(ChannelPromise initPromise, ChannelPromise responsePromise) {
|
||||||
this(initPromise, responsePromise, new DefaultHttp2Connection(false, false));
|
this(initPromise, responsePromise, new DefaultHttp2Connection(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Http2ClientConnectionHandler(ChannelPromise initPromise,
|
private Http2ClientConnectionHandler(ChannelPromise initPromise,
|
||||||
@ -119,7 +119,7 @@ public class Http2ClientConnectionHandler extends AbstractHttp2ConnectionHandler
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception {
|
boolean endOfStream, boolean endOfSegment) throws Http2Exception {
|
||||||
|
|
||||||
// Copy the data into the buffer.
|
// Copy the data into the buffer.
|
||||||
int available = data.readableBytes();
|
int available = data.readableBytes();
|
||||||
|
@ -47,7 +47,7 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler {
|
|||||||
static final byte[] RESPONSE_BYTES = "Hello World".getBytes(CharsetUtil.UTF_8);
|
static final byte[] RESPONSE_BYTES = "Hello World".getBytes(CharsetUtil.UTF_8);
|
||||||
|
|
||||||
public HelloWorldHttp2Handler() {
|
public HelloWorldHttp2Handler() {
|
||||||
this(new DefaultHttp2Connection(true, false));
|
this(new DefaultHttp2Connection(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
private HelloWorldHttp2Handler(Http2Connection connection) {
|
private HelloWorldHttp2Handler(Http2Connection connection) {
|
||||||
@ -77,7 +77,7 @@ public class HelloWorldHttp2Handler extends AbstractHttp2ConnectionHandler {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
|
||||||
boolean endOfStream, boolean endOfSegment, boolean compressed) throws Http2Exception {
|
boolean endOfStream, boolean endOfSegment) throws Http2Exception {
|
||||||
if (endOfStream) {
|
if (endOfStream) {
|
||||||
sendResponse(ctx(), streamId);
|
sendResponse(ctx(), streamId);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user