Http2 draft 17

Motivation:
There was a new draft for HTTP/2.  We should support the new draft.

Modifications:
- Review the HTTP/2 draft 17 specification, and update code to reflect changes.

Result:
Support for HTTP/2 draft 17.
This commit is contained in:
Scott Mitchell 2015-03-16 21:24:24 -07:00
parent b0e7992c16
commit 6dfa1f2d92
6 changed files with 108 additions and 10 deletions

View File

@ -19,6 +19,7 @@ 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.Http2Error.STREAM_CLOSED;
import static io.netty.handler.codec.http2.Http2Exception.connectionError; import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.handler.codec.http2.Http2Exception.streamError; import static io.netty.handler.codec.http2.Http2Exception.streamError;
import static io.netty.handler.codec.http2.Http2PromisedRequestVerifier.ALWAYS_VERIFY;
import static io.netty.handler.codec.http2.Http2Stream.State.CLOSED; import static io.netty.handler.codec.http2.Http2Stream.State.CLOSED;
import static io.netty.util.internal.ObjectUtil.checkNotNull; import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -42,6 +43,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
private final Http2ConnectionEncoder encoder; private final Http2ConnectionEncoder encoder;
private final Http2FrameReader frameReader; private final Http2FrameReader frameReader;
private final Http2FrameListener listener; private final Http2FrameListener listener;
private final Http2PromisedRequestVerifier requestVerifier;
private boolean prefaceReceived; private boolean prefaceReceived;
/** /**
@ -53,6 +55,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
private Http2ConnectionEncoder encoder; private Http2ConnectionEncoder encoder;
private Http2FrameReader frameReader; private Http2FrameReader frameReader;
private Http2FrameListener listener; private Http2FrameListener listener;
private Http2PromisedRequestVerifier requestVerifier = ALWAYS_VERIFY;
@Override @Override
public Builder connection(Http2Connection connection) { public Builder connection(Http2Connection connection) {
@ -89,6 +92,12 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
return this; return this;
} }
@Override
public Http2ConnectionDecoder.Builder requestVerifier(Http2PromisedRequestVerifier requestVerifier) {
this.requestVerifier = requestVerifier;
return this;
}
@Override @Override
public Http2ConnectionDecoder build() { public Http2ConnectionDecoder build() {
return new DefaultHttp2ConnectionDecoder(this); return new DefaultHttp2ConnectionDecoder(this);
@ -105,6 +114,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
lifecycleManager = checkNotNull(builder.lifecycleManager, "lifecycleManager"); lifecycleManager = checkNotNull(builder.lifecycleManager, "lifecycleManager");
encoder = checkNotNull(builder.encoder, "encoder"); encoder = checkNotNull(builder.encoder, "encoder");
listener = checkNotNull(builder.listener, "listener"); listener = checkNotNull(builder.listener, "listener");
requestVerifier = checkNotNull(builder.requestVerifier, "requestVerifier");
if (connection.local().flowController() == null) { if (connection.local().flowController() == null) {
connection.local().flowController( connection.local().flowController(
new DefaultHttp2LocalFlowController(connection, encoder.frameWriter())); new DefaultHttp2LocalFlowController(connection, encoder.frameWriter()));
@ -508,6 +518,22 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
parentStream.id(), parentStream.state()); parentStream.id(), parentStream.state());
} }
if (!requestVerifier.isAuthoritative(ctx, headers)) {
throw streamError(promisedStreamId, PROTOCOL_ERROR,
"Promised request on stream %d for promised stream %d is not authoritative",
streamId, promisedStreamId);
}
if (!requestVerifier.isCacheable(headers)) {
throw streamError(promisedStreamId, PROTOCOL_ERROR,
"Promised request on stream %d for promised stream %d is not known to be cacheable",
streamId, promisedStreamId);
}
if (!requestVerifier.isSafe(headers)) {
throw streamError(promisedStreamId, PROTOCOL_ERROR,
"Promised request on stream %d for promised stream %d is not known to be safe",
streamId, promisedStreamId);
}
// Reserve the push stream based with a priority based on the current stream's priority. // Reserve the push stream based with a priority based on the current stream's priority.
connection.remote().reservePushStream(promisedStreamId, parentStream); connection.remote().reservePushStream(promisedStreamId, parentStream);

View File

@ -86,7 +86,7 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder, Http2Hea
// Default handler for any other types of errors that may have occurred. For example, // Default handler for any other types of errors that may have occurred. For example,
// the the Header builder throws IllegalArgumentException if the key or value was invalid // the the Header builder throws IllegalArgumentException if the key or value was invalid
// for any reason (e.g. the key was an invalid pseudo-header). // for any reason (e.g. the key was an invalid pseudo-header).
throw connectionError(PROTOCOL_ERROR, e, e.getMessage()); throw connectionError(COMPRESSION_ERROR, e, e.getMessage());
} finally { } finally {
try { try {
in.close(); in.close();

View File

@ -37,8 +37,8 @@ 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-16"; public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-17";
public static final String TLS_UPGRADE_PROTOCOL_NAME = "h2-16"; public static final String TLS_UPGRADE_PROTOCOL_NAME = "h2-17";
public static final int PING_FRAME_PAYLOAD_LENGTH = 8; public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
public static final short MAX_UNSIGNED_BYTE = 0xFF; public static final short MAX_UNSIGNED_BYTE = 0xFF;

View File

@ -32,7 +32,6 @@ public interface Http2ConnectionDecoder extends Closeable {
* Builder for new instances of {@link Http2ConnectionDecoder}. * Builder for new instances of {@link Http2ConnectionDecoder}.
*/ */
interface Builder { interface Builder {
/** /**
* Sets the {@link Http2Connection} to be used when building the decoder. * Sets the {@link Http2Connection} to be used when building the decoder.
*/ */
@ -63,6 +62,11 @@ public interface Http2ConnectionDecoder extends Closeable {
*/ */
Builder encoder(Http2ConnectionEncoder encoder); Builder encoder(Http2ConnectionEncoder encoder);
/**
* Sets the {@link Http2PromisedRequestVerifier} used when building the decoder.
*/
Builder requestVerifier(Http2PromisedRequestVerifier requestVerifier);
/** /**
* Creates a new decoder instance. * Creates a new decoder instance.
*/ */

View File

@ -22,7 +22,6 @@ import io.netty.channel.ChannelHandlerContext;
* An listener of HTTP/2 frames. * An listener of HTTP/2 frames.
*/ */
public interface Http2FrameListener { public interface Http2FrameListener {
/** /**
* Handles an inbound {@code DATA} frame. * Handles an inbound {@code DATA} frame.
* *
@ -157,11 +156,8 @@ public interface Http2FrameListener {
/** /**
* Handles an inbound PUSH_PROMISE frame. Only called if END_HEADERS encountered. * Handles an inbound PUSH_PROMISE frame. Only called if END_HEADERS encountered.
* <p> * <p>
* Promised requests MUST be cacheable * Promised requests MUST be authoritative, cacheable, and safe.
* (see <a href="https://tools.ietf.org/html/rfc7231#section-4.2.3">[RFC7231], Section 4.2.3</a>) and * See <a href="https://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-8.2">[RFC http2], Seciton 8.2</a>.
* MUST be safe (see <a href="https://tools.ietf.org/html/rfc7231#section-4.2.1">[RFC7231], Section 4.2.1</a>).
* If these conditions do not hold the application MUST throw a {@link Http2Exception.StreamException} with
* error type {@link Http2Error#PROTOCOL_ERROR}.
* <p> * <p>
* Only one of the following methods will be called for each HEADERS frame sequence. * Only one of the following methods will be called for each HEADERS frame sequence.
* One will be called when the END_HEADERS flag has been received. * One will be called when the END_HEADERS flag has been received.

View File

@ -0,0 +1,72 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package io.netty.handler.codec.http2;
import io.netty.channel.ChannelHandlerContext;
/**
* Provides an extensibility point for users to define the validity of push requests.
* @see <a href="https://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-8.2">[RFC http2], Section 8.2</a>.
*/
public interface Http2PromisedRequestVerifier {
/**
* Determine if a {@link Http2Headers} are authoritative for a particular {@link ChannelHandlerContext}.
* @param ctx The context on which the {@code headers} where received on.
* @param headers The headers to be verified.
* @return {@code true} if the {@code ctx} is authoritative for the {@code headers}, {@code false} otherwise.
* @see
* <a href="https://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-10.1">[RFC http2], Section 10.1</a>.
*/
boolean isAuthoritative(ChannelHandlerContext ctx, Http2Headers headers);
/**
* Determine if a request is cacheable.
* @param headers The headers for a push request.
* @return {@code true} if the request associated with {@code headers} is known to be cacheable,
* {@code false} otherwise.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-4.2.3">[RFC 7231], Section 4.2.3</a>.
*/
boolean isCacheable(Http2Headers headers);
/**
* Determine if a request is safe.
* @param headers The headers for a push request.
* @return {@code true} if the request associated with {@code headers} is known to be safe,
* {@code false} otherwise.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-4.2.1">[RFC 7231], Section 4.2.1</a>.
*/
boolean isSafe(Http2Headers headers);
/**
* A default implementation of {@link Http2PromisedRequestVerifier} which always returns positive responses for
* all verification challenges.
*/
Http2PromisedRequestVerifier ALWAYS_VERIFY = new Http2PromisedRequestVerifier() {
@Override
public boolean isAuthoritative(ChannelHandlerContext ctx, Http2Headers headers) {
return true;
}
@Override
public boolean isCacheable(Http2Headers headers) {
return true;
}
@Override
public boolean isSafe(Http2Headers headers) {
return true;
}
};
}