diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java index 88ecd92341..1c5ee94d0f 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java @@ -14,6 +14,8 @@ */ package io.netty.handler.codec.http2; +import static io.netty.handler.codec.http2.Http2Exception.connectionError; +import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; import io.netty.handler.codec.ByteStringValueConverter; import io.netty.handler.codec.DefaultHeaders; import io.netty.handler.codec.Headers; @@ -25,19 +27,26 @@ public class DefaultHttp2Headers extends DefaultHeaders implements H private static final ByteProcessor HTTP2_NAME_VALIDATOR_PROCESSOR = new ByteProcessor() { @Override public boolean process(byte value) throws Exception { - if (value >= 'A' && value <= 'Z') { - throw new IllegalArgumentException("name must be all lower case but found: " + (char) value); - } - return true; + return value < 'A' || value > 'Z'; } }; private static final NameValidator HTTP2_NAME_VALIDATOR = new NameValidator() { @Override public void validateName(ByteString name) { + final int index; try { - name.forEachByte(HTTP2_NAME_VALIDATOR_PROCESSOR); - } catch (Exception e) { + index = name.forEachByte(HTTP2_NAME_VALIDATOR_PROCESSOR); + } catch (Http2Exception e) { PlatformDependent.throwException(e); + return; + } catch (Throwable t) { + PlatformDependent.throwException(connectionError(PROTOCOL_ERROR, t, + "unexpected error. invalid header name [%s]", name)); + return; + } + + if (index != -1) { + PlatformDependent.throwException(connectionError(PROTOCOL_ERROR, "invalid header name [%s]", name)); } } }; diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersTest.java index b14b4346f0..6a90ed0b9a 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersTest.java @@ -65,7 +65,7 @@ public class DefaultHttp2HeadersTest { } } - @Test(expected = IllegalArgumentException.class) + @Test(expected = Http2Exception.class) public void testHeaderNameValidation() { Http2Headers headers = newHeaders(); diff --git a/example/src/main/java/io/netty/example/http2/Http2ExampleUtil.java b/example/src/main/java/io/netty/example/http2/Http2ExampleUtil.java index e749ef1ea2..ddf5dea4ed 100644 --- a/example/src/main/java/io/netty/example/http2/Http2ExampleUtil.java +++ b/example/src/main/java/io/netty/example/http2/Http2ExampleUtil.java @@ -31,7 +31,7 @@ public final class Http2ExampleUtil { /** * Response header sent in response to the http->http2 cleartext upgrade request. */ - public static final String UPGRADE_RESPONSE_HEADER = "Http-To-Http2-Upgrade"; + public static final String UPGRADE_RESPONSE_HEADER = "http-to-http2-upgrade"; /** * Size of the block to be read from the input stream. diff --git a/example/src/main/java/io/netty/example/http2/helloworld/client/Http2Client.java b/example/src/main/java/io/netty/example/http2/helloworld/client/Http2Client.java index 1e4c08af9a..6408e9593a 100644 --- a/example/src/main/java/io/netty/example/http2/helloworld/client/Http2Client.java +++ b/example/src/main/java/io/netty/example/http2/helloworld/client/Http2Client.java @@ -25,7 +25,9 @@ import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpScheme; import io.netty.handler.codec.http2.Http2SecurityUtil; +import io.netty.handler.codec.http2.HttpConversionUtil; import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; @@ -37,9 +39,9 @@ import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SupportedCipherSuiteFilter; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import io.netty.util.AsciiString; import io.netty.util.CharsetUtil; -import java.net.URI; import java.util.concurrent.TimeUnit; import static io.netty.handler.codec.http.HttpMethod.GET; @@ -106,16 +108,17 @@ public final class Http2Client { HttpResponseHandler responseHandler = initializer.responseHandler(); int streamId = 3; - URI hostName = URI.create((SSL ? "https" : "http") + "://" + HOST + ':' + PORT); + HttpScheme scheme = SSL ? HttpScheme.HTTPS : HttpScheme.HTTP; + AsciiString hostName = new AsciiString(HOST + ':' + PORT); System.err.println("Sending request(s)..."); if (URL != null) { // Create a simple GET request. FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, URL); request.headers().add(HttpHeaderNames.HOST, hostName); + request.headers().add(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), scheme.name()); request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP); request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.DEFLATE); - channel.writeAndFlush(request); - responseHandler.put(streamId, channel.newPromise()); + responseHandler.put(streamId, channel.writeAndFlush(request), channel.newPromise()); streamId += 2; } if (URL2 != null) { @@ -123,10 +126,10 @@ public final class Http2Client { FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST, URL2, Unpooled.copiedBuffer(URL2DATA.getBytes(CharsetUtil.UTF_8))); request.headers().add(HttpHeaderNames.HOST, hostName); + request.headers().add(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), scheme.name()); request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP); request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.DEFLATE); - channel.writeAndFlush(request); - responseHandler.put(streamId, channel.newPromise()); + responseHandler.put(streamId, channel.writeAndFlush(request), channel.newPromise()); streamId += 2; } responseHandler.awaitResponses(5, TimeUnit.SECONDS); diff --git a/example/src/main/java/io/netty/example/http2/helloworld/client/HttpResponseHandler.java b/example/src/main/java/io/netty/example/http2/helloworld/client/HttpResponseHandler.java index 3202da5219..c7aa24b443 100644 --- a/example/src/main/java/io/netty/example/http2/helloworld/client/HttpResponseHandler.java +++ b/example/src/main/java/io/netty/example/http2/helloworld/client/HttpResponseHandler.java @@ -15,6 +15,7 @@ package io.netty.example.http2.helloworld.client; import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.channel.SimpleChannelInboundHandler; @@ -22,6 +23,7 @@ import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http2.HttpConversionUtil; import io.netty.util.CharsetUtil; +import java.util.AbstractMap.SimpleEntry; import java.util.Iterator; import java.util.Map.Entry; import java.util.SortedMap; @@ -33,22 +35,23 @@ import java.util.concurrent.TimeUnit; */ public class HttpResponseHandler extends SimpleChannelInboundHandler { - private SortedMap streamidPromiseMap; + private SortedMap> streamidPromiseMap; public HttpResponseHandler() { - streamidPromiseMap = new TreeMap(); + streamidPromiseMap = new TreeMap>(); } /** * Create an association between an anticipated response stream id and a {@link io.netty.channel.ChannelPromise} * * @param streamId The stream for which a response is expected + * @param writeFuture A future that represent the request write operation * @param promise The promise object that will be used to wait/notify events * @return The previous object associated with {@code streamId} * @see HttpResponseHandler#awaitResponses(long, java.util.concurrent.TimeUnit) */ - public ChannelPromise put(int streamId, ChannelPromise promise) { - return streamidPromiseMap.put(streamId, promise); + public Entry put(int streamId, ChannelFuture writeFuture, ChannelPromise promise) { + return streamidPromiseMap.put(streamId, new SimpleEntry(writeFuture, promise)); } /** @@ -59,10 +62,17 @@ public class HttpResponseHandler extends SimpleChannelInboundHandler> itr = streamidPromiseMap.entrySet().iterator(); + Iterator>> itr = streamidPromiseMap.entrySet().iterator(); while (itr.hasNext()) { - Entry entry = itr.next(); - ChannelPromise promise = entry.getValue(); + Entry> entry = itr.next(); + ChannelFuture writeFuture = entry.getValue().getKey(); + if (!writeFuture.awaitUninterruptibly(timeout, unit)) { + throw new IllegalStateException("Timed out waiting to write for stream id " + entry.getKey()); + } + if (!writeFuture.isSuccess()) { + throw new RuntimeException(writeFuture.cause()); + } + ChannelPromise promise = entry.getValue().getValue(); if (!promise.awaitUninterruptibly(timeout, unit)) { throw new IllegalStateException("Timed out waiting for response on stream id " + entry.getKey()); } @@ -82,8 +92,8 @@ public class HttpResponseHandler extends SimpleChannelInboundHandler entry = streamidPromiseMap.get(streamId); + if (entry == null) { System.err.println("Message received for unknown stream id " + streamId); } else { // Do stuff with the message (for now just print it) @@ -95,7 +105,7 @@ public class HttpResponseHandler extends SimpleChannelInboundHandler