diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServer.java b/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServer.java new file mode 100644 index 000000000000..3fe4a60c5ea7 --- /dev/null +++ b/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServer.java @@ -0,0 +1,74 @@ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.example.http.websocketx.sslserver; + +import java.net.InetSocketAddress; +import java.util.concurrent.Executors; +import java.util.logging.ConsoleHandler; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; + +/** + * A HTTP server which serves Web Socket requests at: + * + * https://localhost:8081/websocket + * + * Open your browser at https://localhost:8081/, then the demo page will be + * loaded and a Web Socket connection will be made automatically. + * + * This server illustrates support for the different web socket specification + * versions and will work with: + * + * + * + * @author The Netty Project + * @author Trustin Lee + * @author Vibul Imtarnasan + * + * @version $Rev$, $Date$ + */ +public class WebSocketSslServer { + public static void main(String[] args) { + ConsoleHandler ch = new ConsoleHandler(); + ch.setLevel(Level.FINE); + Logger.getLogger("").addHandler(ch); + Logger.getLogger("").setLevel(Level.FINE); + + // Configure the server. + ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory( + Executors.newCachedThreadPool(), Executors.newCachedThreadPool())); + + // Set up the event pipeline factory. + bootstrap.setPipelineFactory(new WebSocketSslServerPipelineFactory()); + + // Bind and start to accept incoming connections. + bootstrap.bind(new InetSocketAddress(8081)); + + System.out.println("Web Socket Server started on 8081. Open your browser and navigate to https://localhost:8081/"); + } +} diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServerHandler.java b/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServerHandler.java new file mode 100644 index 000000000000..9b3875b1da98 --- /dev/null +++ b/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServerHandler.java @@ -0,0 +1,152 @@ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.example.http.websocketx.sslserver; + +import static org.jboss.netty.handler.codec.http.HttpHeaders.*; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*; +import static org.jboss.netty.handler.codec.http.HttpMethod.*; +import static org.jboss.netty.handler.codec.http.HttpResponseStatus.*; +import static org.jboss.netty.handler.codec.http.HttpVersion.*; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.handler.codec.http.DefaultHttpResponse; +import org.jboss.netty.handler.codec.http.HttpHeaders; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.HttpResponse; +import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame; +import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame; +import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame; +import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame; +import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; +import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; +import org.jboss.netty.util.CharsetUtil; + +/** + * Handles handshakes and messages + * + * @author The Netty Project + * @author Trustin Lee + * @author Vibul Imtarnasan + * + * @version $Rev$, $Date$ + */ +public class WebSocketSslServerHandler extends SimpleChannelUpstreamHandler { + private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketSslServerHandler.class); + + private static final String WEBSOCKET_PATH = "/websocket"; + + private WebSocketServerHandshaker handshaker = null; + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { + Object msg = e.getMessage(); + if (msg instanceof HttpRequest) { + handleHttpRequest(ctx, (HttpRequest) msg); + } else if (msg instanceof WebSocketFrame) { + handleWebSocketFrame(ctx, (WebSocketFrame) msg); + } + } + + private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception { + // Allow only GET methods. + if (req.getMethod() != GET) { + sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN)); + return; + } + + // Send the demo page and favicon.ico + if (req.getUri().equals("/")) { + HttpResponse res = new DefaultHttpResponse(HTTP_1_1, OK); + + ChannelBuffer content = WebSocketSslServerIndexPage.getContent(getWebSocketLocation(req)); + + res.setHeader(CONTENT_TYPE, "text/html; charset=UTF-8"); + setContentLength(res, content.readableBytes()); + + res.setContent(content); + sendHttpResponse(ctx, req, res); + return; + } else if (req.getUri().equals("/favicon.ico")) { + HttpResponse res = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND); + sendHttpResponse(ctx, req, res); + return; + } + + // Handshake + WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory( + this.getWebSocketLocation(req), null, false); + this.handshaker = wsFactory.newHandshaker(ctx, req); + if (this.handshaker == null) { + wsFactory.sendUnsupportedWebSocketVersionResponse(ctx); + } else { + this.handshaker.executeOpeningHandshake(ctx, req); + } + } + + private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { + + // Check for closing frame + if (frame instanceof CloseWebSocketFrame) { + this.handshaker.executeClosingHandshake(ctx, (CloseWebSocketFrame) frame); + return; + } else if (frame instanceof PingWebSocketFrame) { + ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData())); + return; + } else if (!(frame instanceof TextWebSocketFrame)) { + throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass() + .getName())); + } + + // Send the uppercase string back. + String request = ((TextWebSocketFrame) frame).getText(); + logger.debug(String.format("Channel %s received %s", ctx.getChannel().getId(), request)); + ctx.getChannel().write(new TextWebSocketFrame(request.toUpperCase())); + } + + private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) { + // Generate an error page if response status code is not OK (200). + if (res.getStatus().getCode() != 200) { + res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8)); + setContentLength(res, res.getContent().readableBytes()); + } + + // Send the response and close the connection if necessary. + ChannelFuture f = ctx.getChannel().write(res); + if (!isKeepAlive(req) || res.getStatus().getCode() != 200) { + f.addListener(ChannelFutureListener.CLOSE); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { + e.getCause().printStackTrace(); + e.getChannel().close(); + } + + private String getWebSocketLocation(HttpRequest req) { + return "wss://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH; + } +} diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServerIndexPage.java b/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServerIndexPage.java new file mode 100644 index 000000000000..8fe45996f9f2 --- /dev/null +++ b/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServerIndexPage.java @@ -0,0 +1,73 @@ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.example.http.websocketx.sslserver; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.util.CharsetUtil; + + +/** + * Generates the demo HTML page which is served at http://localhost:8080/ + * + * @author The Netty Project + * @author Trustin Lee + * @author Vibul Imtarnasan + * + * @version $Rev$, $Date$ + */ +public class WebSocketSslServerIndexPage { + + private static final String NEWLINE = "\r\n"; + + public static ChannelBuffer getContent(String webSocketLocation) { + return ChannelBuffers.copiedBuffer( + "Web Socket Test" + NEWLINE + + "" + NEWLINE + + "" + NEWLINE + + "
" + NEWLINE + + "" + + "" + NEWLINE + + "

Output

" + NEWLINE + + "" + NEWLINE + + "
" + NEWLINE + + "" + NEWLINE + + "" + NEWLINE, + CharsetUtil.US_ASCII); + } +} diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServerPipelineFactory.java b/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServerPipelineFactory.java new file mode 100644 index 000000000000..6b3e2bf9ed48 --- /dev/null +++ b/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServerPipelineFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.example.http.websocketx.sslserver; + +import static org.jboss.netty.channel.Channels.*; + +import javax.net.ssl.SSLEngine; + +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.handler.codec.http.HttpChunkAggregator; +import org.jboss.netty.handler.codec.http.HttpRequestDecoder; +import org.jboss.netty.handler.codec.http.HttpResponseEncoder; +import org.jboss.netty.handler.ssl.SslHandler; + +/** + * @author The Netty Project + * @author Trustin Lee + * @author Vibul Imtarnasan + * + * @version $Rev$, $Date$ + */ +public class WebSocketSslServerPipelineFactory implements ChannelPipelineFactory { + @Override + public ChannelPipeline getPipeline() throws Exception { + // Create a default pipeline implementation. + ChannelPipeline pipeline = pipeline(); + + SSLEngine engine = WebSocketSslServerSslContext.getInstance().getServerContext().createSSLEngine(); + engine.setUseClientMode(false); + pipeline.addLast("ssl", new SslHandler(engine)); + + pipeline.addLast("decoder", new HttpRequestDecoder()); + pipeline.addLast("aggregator", new HttpChunkAggregator(65536)); + pipeline.addLast("encoder", new HttpResponseEncoder()); + pipeline.addLast("handler", new WebSocketSslServerHandler()); + return pipeline; + } +} diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServerSslContext.java b/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServerSslContext.java new file mode 100644 index 000000000000..6b668e60dc67 --- /dev/null +++ b/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/WebSocketSslServerSslContext.java @@ -0,0 +1,111 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.example.http.websocketx.sslserver; + +import java.io.FileInputStream; +import java.security.KeyStore; +import java.security.Security; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; + +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; + +/** + * Creates a {@link SSLContext} for just server certificates. + * + * @author The Netty Project + * @author Trustin Lee + * @author Vibul Imtarnasan + * + * @version $Rev$, $Date$ + */ +public class WebSocketSslServerSslContext { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketSslServerSslContext.class); + private static final String PROTOCOL = "TLS"; + private SSLContext _serverContext; + + /** + * Returns the singleton instance for this class + */ + public static WebSocketSslServerSslContext getInstance() { + return SingletonHolder.INSTANCE; + } + + /** + * SingletonHolder is loaded on the first execution of + * Singleton.getInstance() or the first access to SingletonHolder.INSTANCE, + * not before. + * + * See http://en.wikipedia.org/wiki/Singleton_pattern + */ + private static class SingletonHolder { + + public static final WebSocketSslServerSslContext INSTANCE = new WebSocketSslServerSslContext(); + } + + /** + * Constructor for singleton + */ + private WebSocketSslServerSslContext() { + try { + // Key store (Server side certificate) + String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); + if (algorithm == null) { + algorithm = "SunX509"; + } + + SSLContext serverContext = null; + try { + String keyStoreFilePath = System.getProperty("keystore.file.path"); + String keyStoreFilePassword = System.getProperty("keystore.file.password"); + + KeyStore ks = KeyStore.getInstance("JKS"); + FileInputStream fin = new FileInputStream(keyStoreFilePath); + ks.load(fin, keyStoreFilePassword.toCharArray()); + + // Set up key manager factory to use our key store + // Assume key password is the same as the key store file + // password + KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); + kmf.init(ks, keyStoreFilePassword.toCharArray()); + + // Initialise the SSLContext to work with our key managers. + serverContext = SSLContext.getInstance(PROTOCOL); + serverContext.init(kmf.getKeyManagers(), null, null); + } catch (Exception e) { + throw new Error("Failed to initialize the server-side SSLContext", e); + } + _serverContext = serverContext; + + return; + } catch (Exception ex) { + logger.error("Error initializing SslContextManager. " + ex.getMessage(), ex); + System.exit(1); + + } + } + + /** + * Returns the server context with server side key store + */ + public SSLContext getServerContext() { + return _serverContext; + } + +} diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/package-info.java b/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/package-info.java new file mode 100644 index 000000000000..3e91cb30a873 --- /dev/null +++ b/src/main/java/org/jboss/netty/example/http/websocketx/sslserver/package-info.java @@ -0,0 +1,39 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 web socket web server with server SSL. + *

To run this example, follow the steps below: + *

+ *
Step 1. Generate Your Key
+ *
+ * keytool -genkey -keystore mySrvKeystore -keyalg RSA. + * Make sure that you set the key password to be the same the key file password. + *
+ *
Step 2. Specify your key store file and password as system properties
+ *
+ * -Dkeystore.file.path=<path to mySrvKeystore> -Dkeystore.file.password=<password> + *
+ *
Step 3. Run WebSocketSslServer as a Java application
+ *
+ * Once started, you can test the web server against your browser by navigating to https://localhost:8081/ + *
+ *
+ *

To find out more about setting up key stores, refer to this + * giude. + */ +package org.jboss.netty.example.http.websocketx.sslserver; +