diff --git a/example/pom.xml b/example/pom.xml index 70a2da8cbe..7cc434b794 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -69,6 +69,10 @@ javassist runtime + + org.eclipse.jetty.npn + npn-api + diff --git a/example/src/main/java/io/netty/example/spdy/SpdyOrHttpHandler.java b/example/src/main/java/io/netty/example/spdy/SpdyOrHttpHandler.java new file mode 100644 index 0000000000..24329e7122 --- /dev/null +++ b/example/src/main/java/io/netty/example/spdy/SpdyOrHttpHandler.java @@ -0,0 +1,67 @@ +/* + * 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.example.spdy; + +import javax.net.ssl.SSLEngine; + +import io.netty.channel.ChannelInboundHandler; +import org.eclipse.jetty.npn.NextProtoNego; + +import io.netty.handler.codec.spdy.SpdyOrHttpChooser; + +/** + * Negotiates with the browser if SPDY or HTTP is going to be used. Once decided, the Netty pipeline is setup with + * the correct handlers for the selected protocol. + */ +public class SpdyOrHttpHandler extends SpdyOrHttpChooser { + private static final int MAX_CONTENT_LENGTH = 1024 * 100; + + public SpdyOrHttpHandler() { + this(MAX_CONTENT_LENGTH, MAX_CONTENT_LENGTH); + } + + public SpdyOrHttpHandler(int maxSpdyContentLength, int maxHttpContentLength) { + super(maxSpdyContentLength, maxHttpContentLength); + } + + @Override + protected SelectedProtocol getProtocol(SSLEngine engine) { + SpdyServerProvider provider = (SpdyServerProvider) NextProtoNego.get(engine); + String selectedProtocol = provider.getSelectedProtocol(); + + System.out.println("Selected Protocol is " + (selectedProtocol == null ? "Unknown" : selectedProtocol)); + + if (selectedProtocol == null) { + return SpdyOrHttpChooser.SelectedProtocol.UNKNOWN; + } else if (selectedProtocol.equalsIgnoreCase("spdy/3.1")) { + return SpdyOrHttpChooser.SelectedProtocol.SPDY_3_1; + } else if (selectedProtocol.equalsIgnoreCase("spdy/3")) { + return SpdyOrHttpChooser.SelectedProtocol.SPDY_3; + } else if (selectedProtocol.equalsIgnoreCase("http/1.1")) { + return SpdyOrHttpChooser.SelectedProtocol.HTTP_1_1; + } else if (selectedProtocol.equalsIgnoreCase("http/1.0")) { + return SpdyOrHttpChooser.SelectedProtocol.HTTP_1_0; + } else { + return SpdyOrHttpChooser.SelectedProtocol.UNKNOWN; + } + } + + @Override + protected ChannelInboundHandler createHttpRequestHandlerForHttp() { + return new SpdyServerHandler(); + } + +} diff --git a/example/src/main/java/io/netty/example/spdy/SpdyServer.java b/example/src/main/java/io/netty/example/spdy/SpdyServer.java new file mode 100644 index 0000000000..0de13d0481 --- /dev/null +++ b/example/src/main/java/io/netty/example/spdy/SpdyServer.java @@ -0,0 +1,77 @@ +/* + * 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.example.spdy; + +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; + +/** + * A SPDY Server that responds to a GET request with a Hello World. + *

+ * This class must be run with the JVM parameter: {@code java -Xbootclasspath/p: ...}. + * The "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from + * Maven at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions. + * See {@link http://www.eclipse.org/jetty/documentation/current/npn-chapter.html Jetty docs} for more information. + *

+ * Once started, you can test the server with your + * {@link http://en.wikipedia.org/wiki/SPDY#Browser_support_and_usage SPDY enabled web browser} by navigating + * to https://localhost:8443/. + */ +public class SpdyServer { + + private final int port; + + public SpdyServer(int port) { + this.port = port; + } + + public void run() throws Exception { + // Configure the server. + EventLoopGroup bossGroup = new NioEventLoopGroup(); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + ServerBootstrap b = new ServerBootstrap(); + b.option(ChannelOption.SO_BACKLOG, 1024); + b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) + .childHandler(new SpdyServerInitializer()); + + Channel ch = b.bind(port).sync().channel(); + ch.closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } + + public static void main(String[] args) throws Exception { + int port; + if (args.length > 0) { + port = Integer.parseInt(args[0]); + } else { + port = 8443; + } + + System.out.println("SPDY web server started at port " + port + '.'); + System.out.println("Open your SPDY enabled browser and navigate to https://localhost:" + port + '/'); + System.out.println("If using Chrome browser, check your SPDY sessions at chrome://net-internals/#spdy"); + + new SpdyServer(port).run(); + } +} diff --git a/example/src/main/java/io/netty/example/spdy/SpdyServerHandler.java b/example/src/main/java/io/netty/example/spdy/SpdyServerHandler.java new file mode 100644 index 0000000000..9e624b94dd --- /dev/null +++ b/example/src/main/java/io/netty/example/spdy/SpdyServerHandler.java @@ -0,0 +1,74 @@ +/* + * 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.example.spdy; + +import java.util.Date; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.util.CharsetUtil; + +import static io.netty.handler.codec.http.HttpHeaders.Names.*; +import static io.netty.handler.codec.http.HttpHeaders.*; +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static io.netty.handler.codec.http.HttpVersion.*; + +/** + * HTTP handler that responds with a "Hello World" + */ +public class SpdyServerHandler extends SimpleChannelInboundHandler { + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + ctx.flush(); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof HttpRequest) { + HttpRequest req = (HttpRequest) msg; + + if (is100ContinueExpected(req)) { + ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE)); + } + boolean keepAlive = isKeepAlive(req); + + ByteBuf content = Unpooled.copiedBuffer("Hello World " + (new Date()).toString(), CharsetUtil.UTF_8); + + FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, content); + response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); + response.headers().set(CONTENT_LENGTH, response.content().readableBytes()); + + if (!keepAlive) { + ctx.write(response).addListener(ChannelFutureListener.CLOSE); + } else { + response.headers().set(CONNECTION, Values.KEEP_ALIVE); + ctx.write(response); + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + cause.printStackTrace(); + ctx.close(); + } +} diff --git a/example/src/main/java/io/netty/example/spdy/SpdyServerInitializer.java b/example/src/main/java/io/netty/example/spdy/SpdyServerInitializer.java new file mode 100644 index 0000000000..f9412055a6 --- /dev/null +++ b/example/src/main/java/io/netty/example/spdy/SpdyServerInitializer.java @@ -0,0 +1,47 @@ +/* + * 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.example.spdy; + +import javax.net.ssl.SSLEngine; + +import org.eclipse.jetty.npn.NextProtoNego; + +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.example.securechat.SecureChatSslContextFactory; +import io.netty.handler.ssl.SslHandler; + +/** + * Sets up the Netty pipeline + */ +public class SpdyServerInitializer extends ChannelInitializer { + @Override + public void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline p = ch.pipeline(); + + SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine(); + engine.setUseClientMode(false); + p.addLast("ssl", new SslHandler(engine)); + + // Setup NextProtoNego with our server provider + NextProtoNego.put(engine, new SpdyServerProvider()); + NextProtoNego.debug = true; + + // Negotiates with the browser if SPDY or HTTP is going to be used + p.addLast("handler", new SpdyOrHttpHandler()); + } +} diff --git a/example/src/main/java/io/netty/example/spdy/SpdyServerProvider.java b/example/src/main/java/io/netty/example/spdy/SpdyServerProvider.java new file mode 100644 index 0000000000..250d7eedcc --- /dev/null +++ b/example/src/main/java/io/netty/example/spdy/SpdyServerProvider.java @@ -0,0 +1,59 @@ +/* + * 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.example.spdy; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jetty.npn.NextProtoNego.ServerProvider; + +/** + * The Jetty project provides an implementation of the Transport Layer Security (TLS) extension for Next + * Protocol Negotiation (NPN) for OpenJDK 7 or greater. NPN allows the application layer to negotiate which + * protocol to use over the secure connection. + *

+ * This NPN service provider negotiates using SPDY. + *

+ * To enable NPN support, start the JVM with: {@code java -Xbootclasspath/p: ...}. The + * "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from + * Maven at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions. + * + * @see http://www.eclipse.org/jetty/documentation/current/npn-chapter.html + */ +public class SpdyServerProvider implements ServerProvider { + + private String selectedProtocol; + + @Override + public void unsupported() { + // if unsupported, default to http/1.1 + selectedProtocol = "http/1.1"; + } + + @Override + public List protocols() { + return Arrays.asList("spdy/3.1", "spdy/3", "http/1.1"); + } + + @Override + public void protocolSelected(String protocol) { + selectedProtocol = protocol; + } + + public String getSelectedProtocol() { + return selectedProtocol; + } +} diff --git a/example/src/main/java/io/netty/example/spdy/package-info.java b/example/src/main/java/io/netty/example/spdy/package-info.java new file mode 100644 index 0000000000..5155ee2e85 --- /dev/null +++ b/example/src/main/java/io/netty/example/spdy/package-info.java @@ -0,0 +1,33 @@ +/* + * 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. + */ + +/** + * This package contains an example SPDY HTTP web server. + *

+ * This package relies on the Jetty project's implementation of the Transport Layer Security (TLS) extension for Next + * Protocol Negotiation (NPN) for OpenJDK 7 is required. NPN allows the application layer to negotiate which + * protocol, SPDY or HTTP, to use. + *

+ * To start, run {@link SpdyServer} with the JVM parameter: {@code java -Xbootclasspath/p: ...}. + * The "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from + * Maven at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions. + * See {@link http://www.eclipse.org/jetty/documentation/current/npn-chapter.html Jetty docs} for more information. + *

+ * Once started, you can test the server with your + * {@link http://en.wikipedia.org/wiki/SPDY#Browser_support_and_usage SPDY enabled web browser} by navigating + * to https://localhost:8443/. + */ +package io.netty.example.spdy; diff --git a/pom.xml b/pom.xml index 958a304d8b..4a0d697fc0 100644 --- a/pom.xml +++ b/pom.xml @@ -142,6 +142,13 @@ true + + + org.eclipse.jetty.npn + npn-api + 1.1.0.v20120525 + + com.google.protobuf protobuf-java