From 6dfa1f2d9286d9a4cb4d418eeed5c111f40fe968 Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Mon, 16 Mar 2015 21:24:24 -0700 Subject: [PATCH] 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. --- .../http2/DefaultHttp2ConnectionDecoder.java | 26 +++++++ .../http2/DefaultHttp2HeadersDecoder.java | 2 +- .../handler/codec/http2/Http2CodecUtil.java | 4 +- .../codec/http2/Http2ConnectionDecoder.java | 6 +- .../codec/http2/Http2FrameListener.java | 8 +-- .../http2/Http2PromisedRequestVerifier.java | 72 +++++++++++++++++++ 6 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 codec-http2/src/main/java/io/netty/handler/codec/http2/Http2PromisedRequestVerifier.java diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java index d96dd9e24f..feff3c14ba 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java @@ -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.Http2Exception.connectionError; 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.util.internal.ObjectUtil.checkNotNull; import io.netty.buffer.ByteBuf; @@ -42,6 +43,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { private final Http2ConnectionEncoder encoder; private final Http2FrameReader frameReader; private final Http2FrameListener listener; + private final Http2PromisedRequestVerifier requestVerifier; private boolean prefaceReceived; /** @@ -53,6 +55,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { private Http2ConnectionEncoder encoder; private Http2FrameReader frameReader; private Http2FrameListener listener; + private Http2PromisedRequestVerifier requestVerifier = ALWAYS_VERIFY; @Override public Builder connection(Http2Connection connection) { @@ -89,6 +92,12 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { return this; } + @Override + public Http2ConnectionDecoder.Builder requestVerifier(Http2PromisedRequestVerifier requestVerifier) { + this.requestVerifier = requestVerifier; + return this; + } + @Override public Http2ConnectionDecoder build() { return new DefaultHttp2ConnectionDecoder(this); @@ -105,6 +114,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { lifecycleManager = checkNotNull(builder.lifecycleManager, "lifecycleManager"); encoder = checkNotNull(builder.encoder, "encoder"); listener = checkNotNull(builder.listener, "listener"); + requestVerifier = checkNotNull(builder.requestVerifier, "requestVerifier"); if (connection.local().flowController() == null) { connection.local().flowController( new DefaultHttp2LocalFlowController(connection, encoder.frameWriter())); @@ -508,6 +518,22 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { 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. connection.remote().reservePushStream(promisedStreamId, parentStream); diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoder.java index 70f62dc4e1..4f4a3b31db 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoder.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoder.java @@ -86,7 +86,7 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder, Http2Hea // 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 // 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 { try { in.close(); diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java index fc330620d9..308ac81ea6 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java @@ -37,8 +37,8 @@ public final class Http2CodecUtil { public static final int CONNECTION_STREAM_ID = 0; 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_PROTOCOL_NAME = "h2c-16"; - public static final String TLS_UPGRADE_PROTOCOL_NAME = "h2-16"; + public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-17"; + public static final String TLS_UPGRADE_PROTOCOL_NAME = "h2-17"; public static final int PING_FRAME_PAYLOAD_LENGTH = 8; public static final short MAX_UNSIGNED_BYTE = 0xFF; diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionDecoder.java index 9741c50aa4..ee6604ae7d 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionDecoder.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionDecoder.java @@ -32,7 +32,6 @@ public interface Http2ConnectionDecoder extends Closeable { * Builder for new instances of {@link Http2ConnectionDecoder}. */ interface Builder { - /** * Sets the {@link Http2Connection} to be used when building the decoder. */ @@ -63,6 +62,11 @@ public interface Http2ConnectionDecoder extends Closeable { */ Builder encoder(Http2ConnectionEncoder encoder); + /** + * Sets the {@link Http2PromisedRequestVerifier} used when building the decoder. + */ + Builder requestVerifier(Http2PromisedRequestVerifier requestVerifier); + /** * Creates a new decoder instance. */ diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameListener.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameListener.java index a834b866d0..26072a02eb 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameListener.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameListener.java @@ -22,7 +22,6 @@ import io.netty.channel.ChannelHandlerContext; * An listener of HTTP/2 frames. */ public interface Http2FrameListener { - /** * 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. *

- * Promised requests MUST be cacheable - * (see [RFC7231], Section 4.2.3) and - * MUST be safe (see [RFC7231], Section 4.2.1). - * If these conditions do not hold the application MUST throw a {@link Http2Exception.StreamException} with - * error type {@link Http2Error#PROTOCOL_ERROR}. + * Promised requests MUST be authoritative, cacheable, and safe. + * See [RFC http2], Seciton 8.2. *

* 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. diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2PromisedRequestVerifier.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2PromisedRequestVerifier.java new file mode 100644 index 0000000000..927781f910 --- /dev/null +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2PromisedRequestVerifier.java @@ -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 [RFC http2], Section 8.2. + */ +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 + * [RFC http2], Section 10.1. + */ + 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 [RFC 7231], Section 4.2.3. + */ + 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 [RFC 7231], Section 4.2.1. + */ + 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; + } + }; +}