diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameCodec.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameCodec.java index d288e196a6..d49fd92198 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameCodec.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2FrameCodec.java @@ -363,7 +363,7 @@ public class Http2FrameCodec extends ChannelDuplexHandler { dataFrame.streamId(streamId); ctx.fireChannelRead(dataFrame); - // We return the bytes in bytesConsumed() once the stream channel consumed the bytes. + // We return the bytes in consumeBytes() once the stream channel consumed the bytes. return 0; } } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ServerUpgradeCodec.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ServerUpgradeCodec.java index 458de4a6c2..2f3d6de67d 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ServerUpgradeCodec.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ServerUpgradeCodec.java @@ -17,6 +17,7 @@ package io.netty.handler.codec.http2; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.base64.Base64; import io.netty.handler.codec.http.FullHttpRequest; @@ -95,6 +96,25 @@ public class Http2ServerUpgradeCodec implements HttpServerUpgradeHandler.Upgrade this(handlerName, http2Codec.frameCodec().connectionHandler(), http2Codec); } + /** + * Creates the codec using a default name for the connection handler when adding to the + * pipeline. + * + * @param http2Codec the HTTP/2 frame handler. + * @param handlers the handlers that will handle the {@link Http2Frame}s. + */ + public Http2ServerUpgradeCodec(final Http2FrameCodec http2Codec, final ChannelHandler... handlers) { + this(null, http2Codec.connectionHandler(), new ChannelHandlerAdapter() { + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + ctx.pipeline().addLast(http2Codec); + ctx.pipeline().addLast(handlers); + ctx.pipeline().remove(this); + } + }); + } + Http2ServerUpgradeCodec(String handlerName, Http2ConnectionHandler connectionHandler, ChannelHandler upgradeToHandler) { this.handlerName = handlerName; diff --git a/example/src/main/java/io/netty/example/http2/helloworld/frame/server/HelloWorldHttp2Handler.java b/example/src/main/java/io/netty/example/http2/helloworld/frame/server/HelloWorldHttp2Handler.java new file mode 100644 index 0000000000..b7d1e383f0 --- /dev/null +++ b/example/src/main/java/io/netty/example/http2/helloworld/frame/server/HelloWorldHttp2Handler.java @@ -0,0 +1,104 @@ +/* + * Copyright 2016 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.example.http2.helloworld.frame.server; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.DefaultHttp2WindowUpdateFrame; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2Headers; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import io.netty.util.CharsetUtil; + +import static io.netty.buffer.Unpooled.copiedBuffer; +import static io.netty.buffer.Unpooled.unreleasableBuffer; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; + +/** + * A simple handler that responds with the message "Hello World!". + * + *
This example is making use of the "frame codec" http2 API. This API is very experimental and incomplete. + */ +@Sharable +public class HelloWorldHttp2Handler extends ChannelDuplexHandler { + + static final ByteBuf RESPONSE_BYTES = unreleasableBuffer(copiedBuffer("Hello World", CharsetUtil.UTF_8)); + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + super.exceptionCaught(ctx, cause); + cause.printStackTrace(); + ctx.close(); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof Http2HeadersFrame) { + onHeadersRead(ctx, (Http2HeadersFrame) msg); + } else if (msg instanceof Http2DataFrame) { + onDataRead(ctx, (Http2DataFrame) msg); + } else { + super.channelRead(ctx, msg); + } + } + + /** + * If receive a frame with end-of-stream set, send a pre-canned response. + */ + public void onDataRead(ChannelHandlerContext ctx, Http2DataFrame data) throws Exception { + int consumed = data.padding() + data.content().readableBytes(); + int streamId = data.streamId(); + + if (data.isEndStream()) { + sendResponse(ctx, streamId, data.content()); + } else { + // We do not send back the response to the remote-peer, so we need to release it. + data.release(); + } + + // Update the flowcontroller + ctx.write(new DefaultHttp2WindowUpdateFrame(consumed).streamId(streamId)); + } + + /** + * If receive a frame with end-of-stream set, send a pre-canned response. + */ + public void onHeadersRead(ChannelHandlerContext ctx, Http2HeadersFrame headers) + throws Exception { + if (headers.isEndStream()) { + ByteBuf content = ctx.alloc().buffer(); + content.writeBytes(RESPONSE_BYTES.duplicate()); + ByteBufUtil.writeAscii(content, " - via HTTP/2"); + sendResponse(ctx, headers.streamId(), content); + } + } + + /** + * Sends a "Hello World" DATA frame to the client. + */ + private static void sendResponse(ChannelHandlerContext ctx, int streamId, ByteBuf payload) { + // Send a frame for the response status + Http2Headers headers = new DefaultHttp2Headers().status(OK.codeAsText()); + ctx.write(new DefaultHttp2HeadersFrame(headers).streamId(streamId)); + ctx.writeAndFlush(new DefaultHttp2DataFrame(payload, true).streamId(streamId)); + } +} diff --git a/example/src/main/java/io/netty/example/http2/helloworld/frame/server/Http2OrHttpHandler.java b/example/src/main/java/io/netty/example/http2/helloworld/frame/server/Http2OrHttpHandler.java new file mode 100644 index 0000000000..3743e5992c --- /dev/null +++ b/example/src/main/java/io/netty/example/http2/helloworld/frame/server/Http2OrHttpHandler.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016 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.example.http2.helloworld.frame.server; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.example.http2.helloworld.server.HelloWorldHttp1Handler; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http2.Http2FrameCodec; +import io.netty.handler.ssl.ApplicationProtocolNames; +import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; + +/** + * Negotiates with the browser if HTTP2 or HTTP is going to be used. Once decided, the Netty + * pipeline is setup with the correct handlers for the selected protocol. + */ +public class Http2OrHttpHandler extends ApplicationProtocolNegotiationHandler { + + private static final int MAX_CONTENT_LENGTH = 1024 * 100; + + protected Http2OrHttpHandler() { + super(ApplicationProtocolNames.HTTP_1_1); + } + + @Override + protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception { + if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { + ctx.pipeline().addLast(new Http2FrameCodec(true), new HelloWorldHttp2Handler()); + return; + } + + if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) { + ctx.pipeline().addLast(new HttpServerCodec(), + new HttpObjectAggregator(MAX_CONTENT_LENGTH), + new HelloWorldHttp1Handler("ALPN Negotiation")); + return; + } + + throw new IllegalStateException("unknown protocol: " + protocol); + } +} diff --git a/example/src/main/java/io/netty/example/http2/helloworld/frame/server/Http2Server.java b/example/src/main/java/io/netty/example/http2/helloworld/frame/server/Http2Server.java new file mode 100644 index 0000000000..f363721d68 --- /dev/null +++ b/example/src/main/java/io/netty/example/http2/helloworld/frame/server/Http2Server.java @@ -0,0 +1,96 @@ +/* + * Copyright 2016 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.example.http2.helloworld.frame.server; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http2.Http2SecurityUtil; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.netty.handler.ssl.ApplicationProtocolConfig; +import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; +import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; +import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; +import io.netty.handler.ssl.ApplicationProtocolNames; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslProvider; +import io.netty.handler.ssl.SupportedCipherSuiteFilter; +import io.netty.handler.ssl.util.SelfSignedCertificate; + +/** + * A HTTP/2 Server that responds to requests with a Hello World. Once started, you can test the + * server with the example client. + * + *
This example is making use of the "multiplexing" http2 API, where streams are mapped to child
+ * Channels. This API is very experimental and incomplete.
+ */
+public final class Http2Server {
+
+ static final boolean SSL = System.getProperty("ssl") != null;
+
+ static final int PORT = Integer.parseInt(System.getProperty("port", SSL? "8443" : "8080"));
+
+ public static void main(String[] args) throws Exception {
+ // Configure SSL.
+ final SslContext sslCtx;
+ if (SSL) {
+ SslProvider provider = OpenSsl.isAlpnSupported() ? SslProvider.OPENSSL : SslProvider.JDK;
+ SelfSignedCertificate ssc = new SelfSignedCertificate();
+ sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
+ .sslProvider(provider)
+ /* NOTE: the cipher filter may not include all ciphers required by the HTTP/2 specification.
+ * Please refer to the HTTP/2 specification for cipher requirements. */
+ .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
+ .applicationProtocolConfig(new ApplicationProtocolConfig(
+ Protocol.ALPN,
+ // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers.
+ SelectorFailureBehavior.NO_ADVERTISE,
+ // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers.
+ SelectedListenerFailureBehavior.ACCEPT,
+ ApplicationProtocolNames.HTTP_2,
+ ApplicationProtocolNames.HTTP_1_1))
+ .build();
+ } else {
+ sslCtx = null;
+ }
+ // Configure the server.
+ EventLoopGroup group = new NioEventLoopGroup();
+ try {
+ ServerBootstrap b = new ServerBootstrap();
+ b.option(ChannelOption.SO_BACKLOG, 1024);
+ b.group(group)
+ .channel(NioServerSocketChannel.class)
+ .handler(new LoggingHandler(LogLevel.INFO))
+ .childHandler(new Http2ServerInitializer(sslCtx));
+
+ Channel ch = b.bind(PORT).sync().channel();
+
+ System.err.println("Open your HTTP/2-enabled web browser and navigate to " +
+ (SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/');
+
+ ch.closeFuture().sync();
+ } finally {
+ group.shutdownGracefully();
+ }
+ }
+}
diff --git a/example/src/main/java/io/netty/example/http2/helloworld/frame/server/Http2ServerInitializer.java b/example/src/main/java/io/netty/example/http2/helloworld/frame/server/Http2ServerInitializer.java
new file mode 100644
index 0000000000..0fb29b9f35
--- /dev/null
+++ b/example/src/main/java/io/netty/example/http2/helloworld/frame/server/Http2ServerInitializer.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2016 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.example.http2.helloworld.frame.server;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.example.http2.helloworld.server.HelloWorldHttp1Handler;
+import io.netty.handler.codec.http.HttpMessage;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.HttpServerUpgradeHandler;
+import io.netty.handler.codec.http.HttpServerUpgradeHandler.UpgradeCodec;
+import io.netty.handler.codec.http.HttpServerUpgradeHandler.UpgradeCodecFactory;
+import io.netty.handler.codec.http2.Http2CodecUtil;
+import io.netty.handler.codec.http2.Http2FrameCodec;
+import io.netty.handler.codec.http2.Http2ServerUpgradeCodec;
+import io.netty.handler.ssl.SslContext;
+import io.netty.util.AsciiString;
+import io.netty.util.ReferenceCountUtil;
+
+/**
+ * Sets up the Netty pipeline for the example server. Depending on the endpoint config, sets up the
+ * pipeline for NPN or cleartext HTTP upgrade to HTTP/2.
+ */
+public class Http2ServerInitializer extends ChannelInitializer