diff --git a/license/LICENSE.webbit.txt b/license/LICENSE.webbit.txt new file mode 100644 index 0000000000..05ae225fa3 --- /dev/null +++ b/license/LICENSE.webbit.txt @@ -0,0 +1,37 @@ +(BSD License: http://www.opensource.org/licenses/bsd-license) + +Copyright (c) 2011, Joe Walnes, Aslak Hellesøy and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the Webbit nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/src/main/java/org/jboss/netty/example/http/file/HttpStaticFileServerHandler.java b/src/main/java/org/jboss/netty/example/http/file/HttpStaticFileServerHandler.java index 922f0be479..f3cf358bcd 100644 --- a/src/main/java/org/jboss/netty/example/http/file/HttpStaticFileServerHandler.java +++ b/src/main/java/org/jboss/netty/example/http/file/HttpStaticFileServerHandler.java @@ -142,8 +142,11 @@ public class HttpStaticFileServerHandler extends SimpleChannelUpstreamHandler { { SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US); Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince); - if (ifModifiedSinceDate.getTime() == file.lastModified()) - { + + // Only compare up to the second because the datetime format we send to the client does not have milliseconds + long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000; + long fileLastModifiedSeconds = file.lastModified() / 1000; + if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) { sendNotModified(ctx); return; } diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/autobahn/WebSocketServer.java b/src/main/java/org/jboss/netty/example/http/websocketx/autobahn/WebSocketServer.java new file mode 100644 index 0000000000..67cb476e98 --- /dev/null +++ b/src/main/java/org/jboss/netty/example/http/websocketx/autobahn/WebSocketServer.java @@ -0,0 +1,58 @@ +/* + * 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.autobahn; + +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 Web Socket echo server for running the autobahn + * test suite + * + * @author The Netty Project + * @author Trustin Lee + * @author Vibul Imtarnasan + * + * @version $Rev$, $Date$ + */ +public class WebSocketServer { + 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())); + + //bootstrap.setOption("child.tcpNoDelay", true); + + // Set up the event pipeline factory. + bootstrap.setPipelineFactory(new WebSocketServerPipelineFactory()); + + // Bind and start to accept incoming connections. + bootstrap.bind(new InetSocketAddress(9000)); + + System.out.println("Web Socket Server started on 9000. Open your browser and navigate to http://localhost:9000/"); + } +} diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/autobahn/WebSocketServerHandler.java b/src/main/java/org/jboss/netty/example/http/websocketx/autobahn/WebSocketServerHandler.java new file mode 100644 index 0000000000..f3a084282f --- /dev/null +++ b/src/main/java/org/jboss/netty/example/http/websocketx/autobahn/WebSocketServerHandler.java @@ -0,0 +1,139 @@ +/* + * 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.autobahn; + +import static org.jboss.netty.handler.codec.http.HttpHeaders.*; +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.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.BinaryWebSocketFrame; +import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame; +import org.jboss.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame; +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 WebSocketServerHandler extends SimpleChannelUpstreamHandler { + private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandler.class); + + 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; + } + + // 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); + } + return; + } + + private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { + logger.debug(String + .format("Channel %s received %s", ctx.getChannel().getId(), frame.getClass().getSimpleName())); + + if (frame instanceof CloseWebSocketFrame) { + this.handshaker.executeClosingHandshake(ctx, (CloseWebSocketFrame) frame); + } else if (frame instanceof PingWebSocketFrame) { + ctx.getChannel().write( + new PongWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); + } else if (frame instanceof TextWebSocketFrame) { + //String text = ((TextWebSocketFrame) frame).getText(); + ctx.getChannel().write(new TextWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); + } else if (frame instanceof BinaryWebSocketFrame) { + ctx.getChannel().write( + new BinaryWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); + } else if (frame instanceof ContinuationWebSocketFrame) { + ctx.getChannel().write( + new ContinuationWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); + } else if (frame instanceof PongWebSocketFrame) { + // Ignore + } else { + throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass() + .getName())); + } + } + + 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 "ws://" + req.getHeader(HttpHeaders.Names.HOST); + } +} diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/autobahn/WebSocketServerPipelineFactory.java b/src/main/java/org/jboss/netty/example/http/websocketx/autobahn/WebSocketServerPipelineFactory.java new file mode 100644 index 0000000000..e7bfd52999 --- /dev/null +++ b/src/main/java/org/jboss/netty/example/http/websocketx/autobahn/WebSocketServerPipelineFactory.java @@ -0,0 +1,44 @@ +/* + * 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.autobahn; + +import static org.jboss.netty.channel.Channels.*; + +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; + +/** + * @author The Netty Project + * @author Trustin Lee + * @author Vibul Imtarnasan + * + * @version $Rev$, $Date$ + */ +public class WebSocketServerPipelineFactory implements ChannelPipelineFactory { + @Override + public ChannelPipeline getPipeline() throws Exception { + // Create a default pipeline implementation. + ChannelPipeline pipeline = pipeline(); + pipeline.addLast("decoder", new HttpRequestDecoder()); + pipeline.addLast("aggregator", new HttpChunkAggregator(65536)); + pipeline.addLast("encoder", new HttpResponseEncoder()); + pipeline.addLast("handler", new WebSocketServerHandler()); + return pipeline; + } +} diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/autobahn/package-info.java b/src/main/java/org/jboss/netty/example/http/websocketx/autobahn/package-info.java new file mode 100644 index 0000000000..46de920b06 --- /dev/null +++ b/src/main/java/org/jboss/netty/example/http/websocketx/autobahn/package-info.java @@ -0,0 +1,54 @@ +/* + * 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 is intended for use with testing against the Python + * AutoBahn test suite. + * + *
01. Add ppa:twisted-dev/ppa to your system's Software Sources + * + *
02. Install Twisted V11: sudo apt-get install python-twisted + * + *
03. Intall Python Setup Tools: sudo apt-get install python-setuptools + * + *
04. Install AutoBahn: sudo easy_install Autobahn + * + *
05. Get AutoBahn testsuite source code: git clone git@github.com:oberstet/Autobahn.git + * + *
06. Go to AutoBahn directory: cd Autobahn + * + *
07. Checkout stable version: git checkout v0.4.2 + * + *
08. Go to test suite directory: cd testsuite/websockets + * + *
09. Edit fuzzing_clinet_spec.json and set the version to 10.
+ *
+ * {
+ * "servers": [{"agent": "Netty", "hostname": "localhost", "port": 9000, "version": 10}],
+ * "cases": ["*"]
+ * }
+ *
+ *
+ *
10. Run the test python fuzzing_client.py. Note that the actual test case python code is + * located in /usr/local/lib/python2.6/dist-packages/autobahn-0.4.2-py2.6.egg/autobahn/cases + * and not in the checked out git repository. + * + *
11. See the results in reports/servers/index.html
+ */
+package org.jboss.netty.example.http.websocketx.autobahn;
+
diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/client/App.java b/src/main/java/org/jboss/netty/example/http/websocketx/client/App.java
new file mode 100644
index 0000000000..1112c45cf7
--- /dev/null
+++ b/src/main/java/org/jboss/netty/example/http/websocketx/client/App.java
@@ -0,0 +1,136 @@
+/*
+ * 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.client;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.jboss.netty.buffer.ChannelBuffers;
+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.WebSocketSpecificationVersion;
+
+/**
+ * A HTTP client demo app
+ *
+ * @author Vibul Imtarnasan
+ *
+ * @version $Rev$, $Date$
+ */
+public class App {
+
+ public static void main(String[] args) throws Exception {
+ ConsoleHandler ch = new ConsoleHandler();
+ ch.setLevel(Level.FINE);
+ Logger.getLogger("").addHandler(ch);
+ Logger.getLogger("").setLevel(Level.FINE);
+
+ runClient();
+ System.exit(0);
+ }
+
+ /**
+ * Send and receive some messages using a web socket client
+ *
+ * @throws Exception
+ */
+ public static void runClient() throws Exception {
+
+ MyCallbackHandler callbackHandler = new MyCallbackHandler();
+ WebSocketClientFactory factory = new WebSocketClientFactory();
+
+ // Connect with spec version 10 (try changing it to V00 and it will
+ // still work ... fingers crossed ;-)
+ WebSocketClient client = factory.newClient(new URI("ws://localhost:8080/websocket"),
+ WebSocketSpecificationVersion.V10, callbackHandler);
+
+ // Connect
+ System.out.println("WebSocket Client connecting");
+ client.connect().awaitUninterruptibly();
+ Thread.sleep(200);
+
+ // Send 10 messages and wait for responses
+ System.out.println("WebSocket Client sending message");
+ for (int i = 0; i < 10; i++) {
+ client.send(new TextWebSocketFrame("Message #" + i));
+ }
+ Thread.sleep(1000);
+
+ // Ping
+ System.out.println("WebSocket Client sending ping");
+ client.send(new PingWebSocketFrame(ChannelBuffers.copiedBuffer(new byte[] { 1, 2, 3, 4, 5, 6 })));
+ Thread.sleep(1000);
+
+ // Close
+ System.out.println("WebSocket Client sending close");
+ client.send(new CloseWebSocketFrame());
+ Thread.sleep(1000);
+
+ // Disconnect
+ client.disconnect();
+ }
+
+ /**
+ * Our web socket callback handler for this app
+ */
+ public static class MyCallbackHandler implements WebSocketCallback {
+ public boolean connected = false;
+ public ArrayList This is an example web service client.
+ * To run this example, you must first start
+ * org.jboss.netty.example.http.websocketx.server.WebSocketServer
+ * Next, run org.jboss.netty.example.http.websocketx.client.App.
+ */
+package org.jboss.netty.example.http.websocketx.client;
+
diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/server/WebSocketServer.java b/src/main/java/org/jboss/netty/example/http/websocketx/server/WebSocketServer.java
new file mode 100644
index 0000000000..37869480db
--- /dev/null
+++ b/src/main/java/org/jboss/netty/example/http/websocketx/server/WebSocketServer.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.server;
+
+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:
+ *
+ * http://localhost:8080/websocket
+ *
+ * Open your browser at http://localhost:8080/, 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:
+ *
+ * This package contains an example web socket web server.
+ * The web server only handles text, ping and closing frames. For text frames,
+ * it echoes the received text in upper case.
+ * Once started, you can test the web server against your browser by navigating
+ * to http://localhost:8080/
+ * You can also test it with a web socket client. Send web socket traffic to
+ * ws://localhost:8080/websocket.
+ */
+package org.jboss.netty.example.http.websocketx.server;
+
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/HttpHeaders.java b/src/main/java/org/jboss/netty/handler/codec/http/HttpHeaders.java
index 68e15757db..7e2f2c4b41 100644
--- a/src/main/java/org/jboss/netty/handler/codec/http/HttpHeaders.java
+++ b/src/main/java/org/jboss/netty/handler/codec/http/HttpHeaders.java
@@ -236,6 +236,18 @@ public class HttpHeaders {
* {@code "Sec-WebSocket-Protocol"}
*/
public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
+ /**
+ * {@code "Sec-WebSocket-Version"}
+ */
+ public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";
+ /**
+ * {@code "Sec-WebSocket-Key"}
+ */
+ public static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";
+ /**
+ * {@code "Sec-WebSocket-Accept"}
+ */
+ public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept";
/**
* {@code "Server"}
*/
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/BinaryWebSocketFrame.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/BinaryWebSocketFrame.java
new file mode 100644
index 0000000000..2e04c3ce25
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/BinaryWebSocketFrame.java
@@ -0,0 +1,68 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+
+/**
+ * Web Socket frame containing binary data
+ *
+ * @author Vibul Imtarnasan
+ */
+public class BinaryWebSocketFrame extends WebSocketFrame {
+
+ /**
+ * Creates a new empty binary frame.
+ */
+ public BinaryWebSocketFrame() {
+ this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ }
+
+ /**
+ * Creates a new binary frame with the specified binary data. The final
+ * fragment flag is set to true.
+ *
+ * @param binaryData
+ * the content of the frame.
+ */
+ public BinaryWebSocketFrame(ChannelBuffer binaryData) {
+ this.setBinaryData(binaryData);
+ }
+
+ /**
+ * Creates a new binary frame with the specified binary data and the final
+ * fragment flag.
+ *
+ * @param finalFragment
+ * flag indicating if this frame is the final fragment
+ * @param rsv
+ * reserved bits used for protocol extensions
+ * @param binaryData
+ * the content of the frame.
+ */
+ public BinaryWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
+ this.setFinalFragment(finalFragment);
+ this.setRsv(rsv);
+ this.setBinaryData(binaryData);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
+ }
+
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/CloseWebSocketFrame.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/CloseWebSocketFrame.java
new file mode 100644
index 0000000000..cc607df36c
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/CloseWebSocketFrame.java
@@ -0,0 +1,51 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+import org.jboss.netty.buffer.ChannelBuffers;
+
+/**
+ * Web Socket Frame for closing the connection
+ *
+ * @author Vibul Imtarnasan
+ */
+public class CloseWebSocketFrame extends WebSocketFrame {
+
+ /**
+ * Creates a new empty close frame.
+ */
+ public CloseWebSocketFrame() {
+ this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ }
+
+ /**
+ * Creates a new close frame
+ *
+ * @param finalFragment
+ * flag indicating if this frame is the final fragment
+ * @param rsv
+ * reserved bits used for protocol extensions
+ */
+ public CloseWebSocketFrame(boolean finalFragment, int rsv) {
+ this.setFinalFragment(finalFragment);
+ this.setRsv(rsv);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/ContinuationWebSocketFrame.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/ContinuationWebSocketFrame.java
new file mode 100644
index 0000000000..ec7db870f7
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/ContinuationWebSocketFrame.java
@@ -0,0 +1,144 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.util.CharsetUtil;
+
+/**
+ * Web Socket continuation frame containing continuation text or binary data.
+ * This is used for fragmented messages where the contents of a messages is
+ * contained more than 1 frame.
+ *
+ * @author Vibul Imtarnasan
+ */
+public class ContinuationWebSocketFrame extends WebSocketFrame {
+
+ private String aggregatedText = null;
+
+ /**
+ * Creates a new empty continuation frame.
+ */
+ public ContinuationWebSocketFrame() {
+ this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ }
+
+ /**
+ * Creates a new continuation frame with the specified binary data. The
+ * final fragment flag is set to true.
+ *
+ * @param binaryData
+ * the content of the frame.
+ */
+ public ContinuationWebSocketFrame(ChannelBuffer binaryData) {
+ this.setBinaryData(binaryData);
+ }
+
+ /**
+ * Creates a new continuation frame with the specified binary data
+ *
+ * @param finalFragment
+ * flag indicating if this frame is the final fragment
+ * @param rsv
+ * reserved bits used for protocol extensions
+ * @param binaryData
+ * the content of the frame.
+ */
+ public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
+ this.setFinalFragment(finalFragment);
+ this.setRsv(rsv);
+ this.setBinaryData(binaryData);
+ }
+
+ /**
+ * Creates a new continuation frame with the specified binary data
+ *
+ * @param finalFragment
+ * flag indicating if this frame is the final fragment
+ * @param rsv
+ * reserved bits used for protocol extensions
+ * @param binaryData
+ * the content of the frame.
+ * @param aggregatedText
+ * Aggregated text set by decoder on the final continuation frame
+ * of a fragmented text message
+ */
+ public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData, String aggregatedText) {
+ this.setFinalFragment(finalFragment);
+ this.setRsv(rsv);
+ this.setBinaryData(binaryData);
+ this.aggregatedText = aggregatedText;
+ }
+
+ /**
+ * Creates a new continuation frame with the specified text data
+ *
+ * @param finalFragment
+ * flag indicating if this frame is the final fragment
+ * @param rsv
+ * reserved bits used for protocol extensions
+ * @param text
+ * text content of the frame.
+ */
+ public ContinuationWebSocketFrame(boolean finalFragment, int rsv, String text) {
+ this.setFinalFragment(finalFragment);
+ this.setRsv(rsv);
+ this.setText(text);
+ }
+
+ /**
+ * Returns the text data in this frame
+ */
+ public String getText() {
+ if (this.getBinaryData() == null) {
+ return null;
+ }
+ return this.getBinaryData().toString(CharsetUtil.UTF_8);
+ }
+
+ /**
+ * Sets the string for this frame
+ *
+ * @param text
+ * text to store
+ */
+ public void setText(String text) {
+ if (text == null || text.isEmpty()) {
+ this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ } else {
+ this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
+ }
+
+ /**
+ * Aggregated text returned by decoder on the final continuation frame of a
+ * fragmented text message
+ */
+ public String getAggregatedText() {
+ return aggregatedText;
+ }
+
+ public void setAggregatedText(String aggregatedText) {
+ this.aggregatedText = aggregatedText;
+ }
+
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/PingWebSocketFrame.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/PingWebSocketFrame.java
new file mode 100644
index 0000000000..857afd4484
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/PingWebSocketFrame.java
@@ -0,0 +1,67 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+
+/**
+ * Web Socket frame containing binary data
+ *
+ * @author Vibul Imtarnasan
+ */
+public class PingWebSocketFrame extends WebSocketFrame {
+
+ /**
+ * Creates a new empty ping frame.
+ */
+ public PingWebSocketFrame() {
+ this.setFinalFragment(true);
+ this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ }
+
+ /**
+ * Creates a new ping frame with the specified binary data.
+ *
+ * @param binaryData
+ * the content of the frame.
+ */
+ public PingWebSocketFrame(ChannelBuffer binaryData) {
+ this.setBinaryData(binaryData);
+ }
+
+ /**
+ * Creates a new ping frame with the specified binary data
+ *
+ * @param finalFragment
+ * flag indicating if this frame is the final fragment
+ * @param rsv
+ * reserved bits used for protocol extensions
+ * @param binaryData
+ * the content of the frame.
+ */
+ public PingWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
+ this.setFinalFragment(finalFragment);
+ this.setRsv(rsv);
+ this.setBinaryData(binaryData);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
+ }
+
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/PongWebSocketFrame.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/PongWebSocketFrame.java
new file mode 100644
index 0000000000..19ea1350f8
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/PongWebSocketFrame.java
@@ -0,0 +1,65 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+
+/**
+ * Web Socket frame containing binary data
+ *
+ * @author Vibul Imtarnasan
+ */
+public class PongWebSocketFrame extends WebSocketFrame {
+
+ /**
+ * Creates a new empty pong frame.
+ */
+ public PongWebSocketFrame() {
+ this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ }
+
+ /**
+ * Creates a new pong frame with the specified binary data.
+ *
+ * @param binaryData
+ * the content of the frame.
+ */
+ public PongWebSocketFrame(ChannelBuffer binaryData) {
+ this.setBinaryData(binaryData);
+ }
+
+ /**
+ * Creates a new pong frame with the specified binary data
+ *
+ * @param finalFragment
+ * flag indicating if this frame is the final fragment
+ * @param rsv
+ * reserved bits used for protocol extensions
+ * @param binaryData
+ * the content of the frame.
+ */
+ public PongWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
+ this.setFinalFragment(finalFragment);
+ this.setRsv(rsv);
+ this.setBinaryData(binaryData);
+ }
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
+ }
+
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/TextWebSocketFrame.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/TextWebSocketFrame.java
new file mode 100644
index 0000000000..fa16845e0d
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/TextWebSocketFrame.java
@@ -0,0 +1,128 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.util.CharsetUtil;
+
+/**
+ * Web Socket text frame with assumed UTF-8 encoding
+ *
+ * @author Vibul Imtarnasan
+ *
+ */
+public class TextWebSocketFrame extends WebSocketFrame {
+
+ /**
+ * Creates a new empty text frame.
+ */
+ public TextWebSocketFrame() {
+ this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ }
+
+ /**
+ * Creates a new text frame with the specified text string. The final
+ * fragment flag is set to true.
+ *
+ * @param text
+ * String to put in the frame
+ */
+ public TextWebSocketFrame(String text) {
+ if (text == null || text.isEmpty()) {
+ this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ } else {
+ this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
+ }
+ }
+
+ /**
+ * Creates a new text frame with the specified binary data. The final
+ * fragment flag is set to true.
+ *
+ * @param binaryData
+ * the content of the frame. Must be UTF-8 encoded
+ */
+ public TextWebSocketFrame(ChannelBuffer binaryData) {
+ this.setBinaryData(binaryData);
+ }
+
+ /**
+ * Creates a new text frame with the specified text string. The final
+ * fragment flag is set to true.
+ *
+ * @param finalFragment
+ * flag indicating if this frame is the final fragment
+ * @param rsv
+ * reserved bits used for protocol extensions
+ * @param text
+ * String to put in the frame
+ */
+ public TextWebSocketFrame(boolean finalFragment, int rsv, String text) {
+ this.setFinalFragment(finalFragment);
+ this.setRsv(rsv);
+ if (text == null || text.isEmpty()) {
+ this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ } else {
+ this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
+ }
+ }
+
+ /**
+ * Creates a new text frame with the specified binary data. The final
+ * fragment flag is set to true.
+ *
+ * @param finalFragment
+ * flag indicating if this frame is the final fragment
+ * @param rsv
+ * reserved bits used for protocol extensions
+ * @param binaryData
+ * the content of the frame. Must be UTF-8 encoded
+ */
+ public TextWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
+ this.setFinalFragment(finalFragment);
+ this.setRsv(rsv);
+ this.setBinaryData(binaryData);
+ }
+
+ /**
+ * Returns the text data in this frame
+ */
+ public String getText() {
+ if (this.getBinaryData() == null) {
+ return null;
+ }
+ return this.getBinaryData().toString(CharsetUtil.UTF_8);
+ }
+
+ /**
+ * Sets the string for this frame
+ *
+ * @param text
+ * text to store
+ */
+ public void setText(String text) {
+ if (text == null) {
+ throw new NullPointerException("text");
+ }
+ this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "(text: " + getText() + ')';
+ }
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/UTF8Exception.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/UTF8Exception.java
new file mode 100644
index 0000000000..358f52684a
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/UTF8Exception.java
@@ -0,0 +1,35 @@
+/*
+ * Adaptation of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ *
+ * Copyright (c) 2008-2009 Bjoern Hoehrmann
+ * For the detailed instruction on adding add Web Socket support to your HTTP
+ * server, take a look into the WebSocketServer example located in the
+ * {@code org.jboss.netty.example.http.websocket} package.
+ *
+ * @author The Netty Project
+ * @author Mike Heath (mheath@apache.org)
+ * @author Trustin Lee
+ * @version $Rev: 2342 $, $Date: 2010-07-07 14:07:39 +0900 (Wed, 07 Jul 2010) $
+ *
+ * @apiviz.landmark
+ * @apiviz.uses org.jboss.netty.handler.codec.http.websocket.WebSocketFrame
+ */
+public class WebSocket00FrameDecoder extends ReplayingDecoder
+ * For the detailed instruction on adding add Web Socket support to your HTTP
+ * server, take a look into the WebSocketServer example located in the
+ * {@code org.jboss.netty.example.http.websocket} package.
+ *
+ * @author The Netty Project
+ * @author Mike Heath (mheath@apache.org)
+ * @author Trustin Lee
+ * @version $Rev: 2362 $, $Date: 2010-09-09 19:59:22 +0900 (Thu, 09 Sep 2010) $
+ *
+ * @apiviz.landmark
+ * @apiviz.uses org.jboss.netty.handler.codec.http.websocket.WebSocketFrame
+ */
+@Sharable
+public class WebSocket00FrameEncoder extends OneToOneEncoder {
+
+ @Override
+ protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
+ if (msg instanceof WebSocketFrame) {
+ WebSocketFrame frame = (WebSocketFrame) msg;
+ if (frame instanceof TextWebSocketFrame) {
+ // Text frame
+ ChannelBuffer data = frame.getBinaryData();
+ ChannelBuffer encoded = channel.getConfig().getBufferFactory()
+ .getBuffer(data.order(), data.readableBytes() + 2);
+ encoded.writeByte((byte) 0x00);
+ encoded.writeBytes(data, data.readerIndex(), data.readableBytes());
+ encoded.writeByte((byte) 0xFF);
+ return encoded;
+ } else if (frame instanceof CloseWebSocketFrame) {
+ // Close frame
+ ChannelBuffer data = frame.getBinaryData();
+ ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), 2);
+ encoded.writeByte((byte) 0xFF);
+ encoded.writeByte((byte) 0x00);
+ return encoded;
+ } else {
+ // Binary frame
+ ChannelBuffer data = frame.getBinaryData();
+ int dataLen = data.readableBytes();
+ ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), dataLen + 5);
+
+ // Encode type.
+ encoded.writeByte((byte) 0x80);
+
+ // Encode length.
+ int b1 = dataLen >>> 28 & 0x7F;
+ int b2 = dataLen >>> 14 & 0x7F;
+ int b3 = dataLen >>> 7 & 0x7F;
+ int b4 = dataLen & 0x7F;
+ if (b1 == 0) {
+ if (b2 == 0) {
+ if (b3 == 0) {
+ encoded.writeByte(b4);
+ } else {
+ encoded.writeByte(b3 | 0x80);
+ encoded.writeByte(b4);
+ }
+ } else {
+ encoded.writeByte(b2 | 0x80);
+ encoded.writeByte(b3 | 0x80);
+ encoded.writeByte(b4);
+ }
+ } else {
+ encoded.writeByte(b1 | 0x80);
+ encoded.writeByte(b2 | 0x80);
+ encoded.writeByte(b3 | 0x80);
+ encoded.writeByte(b4);
+ }
+
+ // Encode binary data.
+ encoded.writeBytes(data, data.readerIndex(), dataLen);
+ return encoded;
+ }
+ }
+ return msg;
+ }
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java
new file mode 100644
index 0000000000..2b76491502
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java
@@ -0,0 +1,370 @@
+// (BSD License: http://www.opensource.org/licenses/bsd-license)
+//
+// Copyright (c) 2011, Joe Walnes and contributors
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or
+// without modification, are permitted provided that the
+// following conditions are met:
+//
+// * Redistributions of source code must retain the above
+// copyright notice, this list of conditions and the
+// following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the
+// following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// * Neither the name of the Webbit nor the names of
+// its contributors may be used to endorse or promote products
+// derived from this software without specific prior written
+// permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+package org.jboss.netty.handler.codec.http.websocketx;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelFutureListener;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.frame.CorruptedFrameException;
+import org.jboss.netty.handler.codec.frame.TooLongFrameException;
+import org.jboss.netty.handler.codec.replay.ReplayingDecoder;
+import org.jboss.netty.logging.InternalLogger;
+import org.jboss.netty.logging.InternalLoggerFactory;
+
+/**
+ * Decodes a web socket frame from wire protocol version 8 format. This code was
+ * forked from webbit and modified.
+ *
+ * @author Aslak Hellesøy
+ * @author Vibul Imtarnasan
+ */
+public class WebSocket08FrameDecoder extends ReplayingDecoder
+ * Encodes a web socket frame into wire protocol version 8 format. This code was
+ * forked from webbit and modified.
+ *
+ * Performs client side opening and closing handshakes for web socket
+ * specification version draft-ietf-hybi-thewebsocketprotocol- 00
+ *
+ * A very large portion of this code was taken from the Netty 3.2 HTTP example.
+ *
+ * Sends the opening request to the server:
+ *
+ * Process server response:
+ *
+ * Performs client side opening and closing handshakes for web socket
+ * specification version draft-ietf-hybi-thewebsocketprotocol- 10
+ *
+ * Sends the opening request to the server:
+ *
+ * Process server response:
+ *
+ * Performs server side opening and closing handshakes for web socket
+ * specification version draft-ietf-hybi-thewebsocketprotocol- 00
+ *
+ * A very large portion of this code was taken from the Netty 3.2 HTTP example.
+ *
+ * Handle the web socket handshake for the web socket specification HyBi
+ * version 0 and lower. This standard is really a rehash of hixie-76 and hixie-75.
+ *
+ * Browser request to the server:
+ *
+ * Server response:
+ *
+ * Performs server side opening and closing handshakes for web socket
+ * specification version draft-ietf-hybi-thewebsocketprotocol- 10
+ *
+ * Handle the web socket handshake for the web socket specification HyBi
+ * version 8 to 10. Version 8, 9 and 10 share the same wire protocol.
+ *
+ * Browser request to the server:
+ *
+ * Server response:
+ *
+ * Versions of the web socket specification.
+ *
+ * A specification is tied to one wire protocol version but a protocol version
+ * may have use by more than 1 version of the specification.
+ *
+ * This package supports different web socket specification versions (hence the X suffix).
+ * The specification current supported are:
+ *
+ * In the future, as the specification develops, more versions will be supported.
+ * This contrasts the org.jboss.netty.handler.codec.http.websocket package which only
+ * supports draft-ietf-hybi-thewebsocketprotocol-00.
+ *
+ * For the detailed instruction on adding add Web Socket support to your HTTP
+ * server, take a look into the WebSocketServerX example located in the
+ * {@code org.jboss.netty.example.http.websocket} package.
+ *
+ *
+ *
+ * @author The Netty Project
+ * @author Trustin Lee
+ * @author Vibul Imtarnasan
+ *
+ * @version $Rev$, $Date$
+ */
+public class WebSocketServer {
+ 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 WebSocketServerPipelineFactory());
+
+ // Bind and start to accept incoming connections.
+ bootstrap.bind(new InetSocketAddress(8080));
+
+ System.out.println("Web Socket Server started on 8080. Open your browser and navigate to http://localhost:8080/");
+ }
+}
diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/server/WebSocketServerHandler.java b/src/main/java/org/jboss/netty/example/http/websocketx/server/WebSocketServerHandler.java
new file mode 100644
index 0000000000..d2b2d6ac85
--- /dev/null
+++ b/src/main/java/org/jboss/netty/example/http/websocketx/server/WebSocketServerHandler.java
@@ -0,0 +1,153 @@
+/*
+ * 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.server;
+
+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 WebSocketServerHandler extends SimpleChannelUpstreamHandler {
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandler.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 = WebSocketServerIndexPage.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);
+ }
+ return;
+ }
+
+ 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 "ws://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH;
+ }
+}
diff --git a/src/main/java/org/jboss/netty/example/http/websocketx/server/WebSocketServerIndexPage.java b/src/main/java/org/jboss/netty/example/http/websocketx/server/WebSocketServerIndexPage.java
new file mode 100644
index 0000000000..179df6b633
--- /dev/null
+++ b/src/main/java/org/jboss/netty/example/http/websocketx/server/WebSocketServerIndexPage.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.server;
+
+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 WebSocketServerIndexPage {
+
+ private static final String NEWLINE = "\r\n";
+
+ public static ChannelBuffer getContent(String webSocketLocation) {
+ return ChannelBuffers.copiedBuffer(
+ "
+ * GET /demo HTTP/1.1
+ * Upgrade: WebSocket
+ * Connection: Upgrade
+ * Host: example.com
+ * Origin: http://example.com
+ * Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
+ * Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
+ *
+ * ^n:ds[4U
+ *
+ *
+ * @param ctx
+ * Channel context
+ * @param channel
+ * Channel into which we can write our request
+ */
+ @Override
+ public void beginOpeningHandshake(ChannelHandlerContext ctx, Channel channel) {
+ // Make keys
+ int spaces1 = createRandomNumber(1, 12);
+ int spaces2 = createRandomNumber(1, 12);
+
+ int max1 = Integer.MAX_VALUE / spaces1;
+ int max2 = Integer.MAX_VALUE / spaces2;
+
+ int number1 = createRandomNumber(0, max1);
+ int number2 = createRandomNumber(0, max2);
+
+ int product1 = number1 * spaces1;
+ int product2 = number2 * spaces2;
+
+ String key1 = Integer.toString(product1);
+ String key2 = Integer.toString(product2);
+
+ key1 = insertRandomCharacters(key1);
+ key2 = insertRandomCharacters(key2);
+
+ key1 = insertSpaces(key1, spaces1);
+ key2 = insertSpaces(key2, spaces2);
+
+ byte[] key3 = createRandomBytes(8);
+
+ ByteBuffer buffer = ByteBuffer.allocate(4);
+ buffer.putInt(number1);
+ byte[] number1Array = buffer.array();
+ buffer = ByteBuffer.allocate(4);
+ buffer.putInt(number2);
+ byte[] number2Array = buffer.array();
+
+ byte[] challenge = new byte[16];
+ System.arraycopy(number1Array, 0, challenge, 0, 4);
+ System.arraycopy(number2Array, 0, challenge, 4, 4);
+ System.arraycopy(key3, 0, challenge, 8, 8);
+ this.expectedChallengeResponseBytes = md5(challenge);
+
+ // Get path
+ URI wsURL = this.getWebSocketURL();
+ String path = wsURL.getPath();
+ if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
+ path = wsURL.getPath() + "?" + wsURL.getQuery();
+ }
+
+ // Format request
+ HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
+ request.addHeader(Names.UPGRADE, Values.WEBSOCKET);
+ request.addHeader(Names.CONNECTION, Values.UPGRADE);
+ request.addHeader(Names.HOST, wsURL.getHost());
+ request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost());
+ request.addHeader(Names.SEC_WEBSOCKET_KEY1, key1);
+ request.addHeader(Names.SEC_WEBSOCKET_KEY2, key2);
+ if (this.getSubProtocolRequest() != null && !this.getSubProtocolRequest().equals("")) {
+ request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.getSubProtocolRequest());
+ }
+ request.setContent(ChannelBuffers.copiedBuffer(key3));
+
+ channel.write(request);
+
+ ctx.getPipeline().replace("encoder", "ws-encoder", new WebSocket00FrameEncoder());
+ }
+
+ /**
+ *
+ * HTTP/1.1 101 WebSocket Protocol Handshake
+ * Upgrade: WebSocket
+ * Connection: Upgrade
+ * Sec-WebSocket-Origin: http://example.com
+ * Sec-WebSocket-Location: ws://example.com/demo
+ * Sec-WebSocket-Protocol: sample
+ *
+ * 8jKS'y:G*Co,Wxa-
+ *
+ *
+ * @param ctx
+ * Channel context
+ * @param response
+ * HTTP response returned from the server for the request sent by
+ * beginOpeningHandshake00().
+ * @throws WebSocketHandshakeException
+ */
+ @Override
+ public void endOpeningHandshake(ChannelHandlerContext ctx, HttpResponse response)
+ throws WebSocketHandshakeException {
+ final HttpResponseStatus status = new HttpResponseStatus(101, "WebSocket Protocol Handshake");
+
+ if (!response.getStatus().equals(status)) {
+ throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
+ }
+
+ String upgrade = response.getHeader(Names.UPGRADE);
+ if (upgrade == null || !upgrade.equals(Values.WEBSOCKET)) {
+ throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
+ + response.getHeader(Names.UPGRADE));
+ }
+
+ String connection = response.getHeader(Names.CONNECTION);
+ if (connection == null || !connection.equals(Values.UPGRADE)) {
+ throw new WebSocketHandshakeException("Invalid handshake response connection: "
+ + response.getHeader(Names.CONNECTION));
+ }
+
+ byte[] challenge = response.getContent().array();
+ if (!Arrays.equals(challenge, expectedChallengeResponseBytes)) {
+ throw new WebSocketHandshakeException("Invalid challenge");
+ }
+
+ String protocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
+ this.setSubProtocolResponse(protocol);
+
+ ctx.getPipeline().replace("decoder", "ws-decoder", new WebSocket00FrameDecoder());
+
+ this.setOpenningHandshakeCompleted(true);
+ return;
+ }
+
+ private String insertRandomCharacters(String key) {
+ int count = createRandomNumber(1, 12);
+
+ char[] randomChars = new char[count];
+ int randCount = 0;
+ while (randCount < count) {
+ int rand = (int) (Math.random() * 0x7e + 0x21);
+ if (((0x21 < rand) && (rand < 0x2f)) || ((0x3a < rand) && (rand < 0x7e))) {
+ randomChars[randCount] = (char) rand;
+ randCount += 1;
+ }
+ }
+
+ for (int i = 0; i < count; i++) {
+ int split = createRandomNumber(0, key.length());
+ String part1 = key.substring(0, split);
+ String part2 = key.substring(split);
+ key = part1 + randomChars[i] + part2;
+ }
+
+ return key;
+ }
+
+ private String insertSpaces(String key, int spaces) {
+ for (int i = 0; i < spaces; i++) {
+ int split = createRandomNumber(1, key.length() - 1);
+ String part1 = key.substring(0, split);
+ String part2 = key.substring(split);
+ key = part1 + " " + part2;
+ }
+
+ return key;
+ }
+
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketClientHandshaker10.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketClientHandshaker10.java
new file mode 100644
index 0000000000..5a6db45850
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketClientHandshaker10.java
@@ -0,0 +1,194 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+import java.net.URI;
+
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
+import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
+import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
+import org.jboss.netty.handler.codec.http.HttpMethod;
+import org.jboss.netty.handler.codec.http.HttpRequest;
+import org.jboss.netty.handler.codec.http.HttpResponse;
+import org.jboss.netty.handler.codec.http.HttpResponseStatus;
+import org.jboss.netty.handler.codec.http.HttpVersion;
+import org.jboss.netty.logging.InternalLogger;
+import org.jboss.netty.logging.InternalLoggerFactory;
+import org.jboss.netty.util.CharsetUtil;
+
+/**
+ *
+ * GET /chat HTTP/1.1
+ * Host: server.example.com
+ * Upgrade: websocket
+ * Connection: Upgrade
+ * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+ * Sec-WebSocket-Origin: http://example.com
+ * Sec-WebSocket-Protocol: chat, superchat
+ * Sec-WebSocket-Version: 8
+ *
+ *
+ * @param ctx
+ * Channel context
+ * @param channel
+ * Channel into which we can write our request
+ */
+ @Override
+ public void beginOpeningHandshake(ChannelHandlerContext ctx, Channel channel) {
+ // Get path
+ URI wsURL = this.getWebSocketURL();
+ String path = wsURL.getPath();
+ if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
+ path = wsURL.getPath() + "?" + wsURL.getQuery();
+ }
+
+ // Get 16 bit nonce and base 64 encode it
+ byte[] nonce = createRandomBytes(16);
+ String key = base64Encode(nonce);
+
+ String acceptSeed = key + MAGIC_GUID;
+ byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
+ this.expectedChallengeResponseString = base64Encode(sha1);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("HyBi10 Client Handshake key: %s. Expected response: %s.", key,
+ this.expectedChallengeResponseString));
+ }
+
+ // Format request
+ HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
+ request.addHeader(Names.UPGRADE, Values.WEBSOCKET.toLowerCase());
+ request.addHeader(Names.CONNECTION, Values.UPGRADE);
+ request.addHeader(Names.SEC_WEBSOCKET_KEY, key);
+ request.addHeader(Names.HOST, wsURL.getHost());
+ request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost());
+ if (protocol != null && !protocol.equals("")) {
+ request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
+ }
+ request.addHeader(Names.SEC_WEBSOCKET_VERSION, "8");
+
+ channel.write(request);
+
+ ctx.getPipeline().replace("encoder", "ws-encoder", new WebSocket08FrameEncoder(true));
+ return;
+ }
+
+ /**
+ *
+ * HTTP/1.1 101 Switching Protocols
+ * Upgrade: websocket
+ * Connection: Upgrade
+ * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
+ * Sec-WebSocket-Protocol: chat
+ *
+ *
+ * @param ctx
+ * Channel context
+ * @param response
+ * HTTP response returned from the server for the request sent by
+ * beginOpeningHandshake00().
+ * @throws WebSocketHandshakeException
+ */
+ @Override
+ public void endOpeningHandshake(ChannelHandlerContext ctx, HttpResponse response)
+ throws WebSocketHandshakeException {
+ final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols");
+
+ if (!response.getStatus().equals(status)) {
+ throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
+ }
+
+ String upgrade = response.getHeader(Names.UPGRADE);
+ if (upgrade == null || !upgrade.equals(Values.WEBSOCKET.toLowerCase())) {
+ throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
+ + response.getHeader(Names.UPGRADE));
+ }
+
+ String connection = response.getHeader(Names.CONNECTION);
+ if (connection == null || !connection.equals(Values.UPGRADE)) {
+ throw new WebSocketHandshakeException("Invalid handshake response connection: "
+ + response.getHeader(Names.CONNECTION));
+ }
+
+ String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT);
+ if (accept == null || !accept.equals(this.expectedChallengeResponseString)) {
+ throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept,
+ this.expectedChallengeResponseString));
+ }
+
+ ctx.getPipeline().replace("decoder", "ws-decoder", new WebSocket08FrameDecoder(false, this.allowExtensions));
+
+ this.setOpenningHandshakeCompleted(true);
+ return;
+ }
+
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java
new file mode 100644
index 0000000000..341a20e676
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java
@@ -0,0 +1,56 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+import java.net.URI;
+
+/**
+ * Instances the appropriate handshake class to use for clients
+ *
+ * @author The Netty Project
+ */
+public class WebSocketClientHandshakerFactory {
+
+ /**
+ * Instances a new handshaker
+ *
+ * @param webSocketURL
+ * URL for web socket communications. e.g
+ * "ws://myhost.com/mypath". Subsequent web socket frames will be
+ * sent to this URL.
+ * @param version
+ * Version of web socket specification to use to connect to the
+ * server
+ * @param subProtocol
+ * Sub protocol request sent to the server. Null if no
+ * sub-protocol support is required.
+ * @param allowExtensions
+ * Allow extensions to be used in the reserved bits of the web socket frame
+ * @throws WebSocketHandshakeException
+ */
+ public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketSpecificationVersion version,
+ String subProtocol, boolean allowExtensions) throws WebSocketHandshakeException {
+ if (version == WebSocketSpecificationVersion.V10) {
+ return new WebSocketClientHandshaker10(webSocketURL, version, subProtocol, allowExtensions);
+ }
+ if (version == WebSocketSpecificationVersion.V00) {
+ return new WebSocketClientHandshaker00(webSocketURL, version, subProtocol);
+ }
+
+ throw new WebSocketHandshakeException("Protocol version " + version.toString() + " not supported.");
+
+ }
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketFrame.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketFrame.java
new file mode 100644
index 0000000000..2421a60850
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketFrame.java
@@ -0,0 +1,82 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Base class for web socket frames
+ *
+ * @author The Netty Project
+ */
+public abstract class WebSocketFrame {
+
+ /**
+ * Flag to indicate if this frame is the final fragment in a message. The
+ * first fragment (frame) may also be the final fragment.
+ */
+ private boolean finalFragment = true;
+
+ /**
+ * RSV1, RSV2, RSV3 used for extensions
+ */
+ private int rsv = 0;
+
+ /**
+ * Contents of this frame
+ */
+ private ChannelBuffer binaryData;
+
+ /**
+ * Returns binary data
+ */
+ public ChannelBuffer getBinaryData() {
+ return binaryData;
+ }
+
+ /**
+ * Sets the binary data for this frame
+ */
+ public void setBinaryData(ChannelBuffer binaryData) {
+ this.binaryData = binaryData;
+ }
+
+ /**
+ * Flag to indicate if this frame is the final fragment in a message. The
+ * first fragment (frame) may also be the final fragment.
+ */
+ public boolean isFinalFragment() {
+ return finalFragment;
+ }
+
+ public void setFinalFragment(boolean finalFragment) {
+ this.finalFragment = finalFragment;
+ }
+
+ /**
+ * Bits used for extensions to the standard.
+ */
+ public int getRsv() {
+ return rsv;
+ }
+
+ public void setRsv(int rsv) {
+ this.rsv = rsv;
+ }
+
+
+
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketFrameType.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketFrameType.java
new file mode 100644
index 0000000000..8e80602045
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketFrameType.java
@@ -0,0 +1,25 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+/**
+ * Type of web socket frames
+ *
+ * @author The Netty Project
+ */
+public enum WebSocketFrameType {
+ TEXT, BINARY, PING, PONG, CLOSE, CONTINUATION
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketHandshakeException.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketHandshakeException.java
new file mode 100644
index 0000000000..db9f768615
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketHandshakeException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+/**
+ * Exception during handshaking process
+ *
+ * @author The Netty Project
+ */
+public class WebSocketHandshakeException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public WebSocketHandshakeException(String s) {
+ super(s);
+ }
+
+ public WebSocketHandshakeException(String s, Throwable throwable) {
+ super(s, throwable);
+ }
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java
new file mode 100644
index 0000000000..29656ae060
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java
@@ -0,0 +1,191 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.base64.Base64;
+import org.jboss.netty.handler.codec.http.HttpRequest;
+import org.jboss.netty.util.CharsetUtil;
+
+/**
+ * Base class for server side web socket opening and closing handshakes
+ *
+ * @author The Netty Project
+ */
+public abstract class WebSocketServerHandshaker {
+
+ private String webSocketURL;
+
+ private String subProtocols;
+
+ private String[] subProtocolsArray = null;
+
+ private WebSocketSpecificationVersion version = WebSocketSpecificationVersion.UNKNOWN;
+
+ /**
+ * Constructor specifying the destination web socket location
+ *
+ * @param webSocketURL
+ * URL for web socket communications. e.g
+ * "ws://myhost.com/mypath". Subsequent web socket frames will be
+ * sent to this URL.
+ * @param subProtocols
+ * CSV of supported protocols. Null if sub protocols not
+ * supported.
+ */
+ public WebSocketServerHandshaker(String webSocketURL, String subProtocols) {
+ this.webSocketURL = webSocketURL;
+ this.subProtocols = subProtocols;
+
+ if (this.subProtocols != null) {
+ this.subProtocolsArray = subProtocols.split(",");
+ for (int i = 0; i < this.subProtocolsArray.length; i++) {
+ this.subProtocolsArray[i] = this.subProtocolsArray[i].trim();
+ }
+ }
+ return;
+ }
+
+ /**
+ * Returns the URL of the web socket
+ */
+ public String getWebSocketURL() {
+ return webSocketURL;
+ }
+
+ public void setWebSocketURL(String webSocketURL) {
+ this.webSocketURL = webSocketURL;
+ }
+
+ /**
+ * Returns the CSV of supported sub protocols
+ */
+ public String getSubProtocols() {
+ return subProtocols;
+ }
+
+ public void setSubProtocols(String subProtocols) {
+ this.subProtocols = subProtocols;
+ }
+
+ /**
+ * Returns the version of the specification being supported
+ */
+ public WebSocketSpecificationVersion getVersion() {
+ return version;
+ }
+
+ public void setVersion(WebSocketSpecificationVersion version) {
+ this.version = version;
+ }
+
+ /**
+ * Performs the opening handshake
+ *
+ * @param ctx
+ * Context
+ * @param req
+ * HTTP Request
+ * @throws NoSuchAlgorithmException
+ */
+ public abstract void executeOpeningHandshake(ChannelHandlerContext ctx, HttpRequest req);
+
+ /**
+ * Performs the closing handshake
+ *
+ * @param ctx
+ * Context
+ * @param frame
+ * Closing Frame that was received
+ */
+ public abstract void executeClosingHandshake(ChannelHandlerContext ctx, CloseWebSocketFrame frame);
+
+ /**
+ * Performs an MD5 hash
+ *
+ * @param bytes
+ * Data to hash
+ * @return Hashed data
+ */
+ protected byte[] md5(byte[] bytes) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ return md.digest(bytes);
+ } catch (NoSuchAlgorithmException e) {
+ throw new InternalError("MD5 not supported on this platform");
+ }
+ }
+
+ /**
+ * SHA-1 hashing. Instance this we think it is not thread safe
+ *
+ * @param bytes
+ * byte to hash
+ * @return hashed
+ */
+ protected byte[] sha1(byte[] bytes) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA1");
+ return md.digest(bytes);
+ } catch (NoSuchAlgorithmException e) {
+ throw new InternalError("SHA-1 not supported on this platform");
+ }
+ }
+
+ /**
+ * Base 64 encoding
+ *
+ * @param bytes
+ * Bytes to encode
+ * @return encoded string
+ */
+ protected String base64Encode(byte[] bytes) {
+ ChannelBuffer hashed = ChannelBuffers.wrappedBuffer(bytes);
+ return Base64.encode(hashed).toString(CharsetUtil.UTF_8);
+ }
+
+ /**
+ * Selects the first matching supported sub protocol
+ *
+ * @param requestedSubProtocol
+ * CSV of protocols to be supported. e.g. "chat, superchat"
+ * @return First matching supported sub protocol. Null if not found.
+ */
+ protected String selectSubProtocol(String requestedSubProtocol) {
+ if (requestedSubProtocol == null || this.subProtocolsArray == null) {
+ return null;
+ }
+
+ String[] requesteSubProtocolsArray = requestedSubProtocol.split(",");
+ for (int i = 0; i < requesteSubProtocolsArray.length; i++) {
+ String requesteSubProtocol = requesteSubProtocolsArray[i].trim();
+
+ for (String supportedSubProtocol : this.subProtocolsArray) {
+ if (requesteSubProtocol.equals(supportedSubProtocol)) {
+ return requesteSubProtocol;
+ }
+ }
+ }
+
+ // No match found
+ return null;
+ }
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java
new file mode 100644
index 0000000000..cfbbcd3f0a
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java
@@ -0,0 +1,205 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
+import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.ORIGIN;
+import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY1;
+import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY2;
+import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_LOCATION;
+import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_ORIGIN;
+import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_PROTOCOL;
+import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_LOCATION;
+import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_ORIGIN;
+import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_PROTOCOL;
+import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
+import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
+
+import java.security.NoSuchAlgorithmException;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
+import org.jboss.netty.handler.codec.http.HttpRequest;
+import org.jboss.netty.handler.codec.http.HttpResponse;
+import org.jboss.netty.handler.codec.http.HttpResponseStatus;
+import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
+import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
+import org.jboss.netty.logging.InternalLogger;
+import org.jboss.netty.logging.InternalLoggerFactory;
+
+/**
+ *
+ * GET /demo HTTP/1.1
+ * Upgrade: WebSocket
+ * Connection: Upgrade
+ * Host: example.com
+ * Origin: http://example.com
+ * Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
+ * Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
+ *
+ * ^n:ds[4U
+ *
+ *
+ *
+ * HTTP/1.1 101 WebSocket Protocol Handshake
+ * Upgrade: WebSocket
+ * Connection: Upgrade
+ * Sec-WebSocket-Origin: http://example.com
+ * Sec-WebSocket-Location: ws://example.com/demo
+ * Sec-WebSocket-Protocol: sample
+ *
+ * 8jKS'y:G*Co,Wxa-
+ *
+ *
+ * @param ctx
+ * Channel context
+ * @param req
+ * HTTP request
+ * @throws NoSuchAlgorithmException
+ */
+ @Override
+ public void executeOpeningHandshake(ChannelHandlerContext ctx, HttpRequest req) {
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Channel %s web socket spec version 00 handshake", ctx.getChannel().getId()));
+ }
+ this.setVersion(WebSocketSpecificationVersion.V00);
+
+ // Serve the WebSocket handshake request.
+ if (!Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION))
+ || !WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) {
+ return;
+ }
+
+ // Hixie 75 does not contain these headers while Hixie 76 does
+ boolean isHixie76 = req.containsHeader(SEC_WEBSOCKET_KEY1) && req.containsHeader(SEC_WEBSOCKET_KEY2);
+
+ // Create the WebSocket handshake response.
+ HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101,
+ isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake"));
+ res.addHeader(Names.UPGRADE, WEBSOCKET);
+ res.addHeader(CONNECTION, Values.UPGRADE);
+
+ // Fill in the headers and contents depending on handshake method.
+ if (isHixie76) {
+ // New handshake method with a challenge:
+ res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
+ res.addHeader(SEC_WEBSOCKET_LOCATION, this.getWebSocketURL());
+ String protocol = req.getHeader(SEC_WEBSOCKET_PROTOCOL);
+ if (protocol != null) {
+ res.addHeader(SEC_WEBSOCKET_PROTOCOL, selectSubProtocol(protocol));
+ }
+
+ // Calculate the answer of the challenge.
+ String key1 = req.getHeader(SEC_WEBSOCKET_KEY1);
+ String key2 = req.getHeader(SEC_WEBSOCKET_KEY2);
+ int a = (int) (Long.parseLong(key1.replaceAll("[^0-9]", "")) / key1.replaceAll("[^ ]", "").length());
+ int b = (int) (Long.parseLong(key2.replaceAll("[^0-9]", "")) / key2.replaceAll("[^ ]", "").length());
+ long c = req.getContent().readLong();
+ ChannelBuffer input = ChannelBuffers.buffer(16);
+ input.writeInt(a);
+ input.writeInt(b);
+ input.writeLong(c);
+ ChannelBuffer output = ChannelBuffers.wrappedBuffer(this.md5(input.array()));
+ res.setContent(output);
+ } else {
+ // Old Hixie 75 handshake method with no challenge:
+ res.addHeader(WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
+ res.addHeader(WEBSOCKET_LOCATION, this.getWebSocketURL());
+ String protocol = req.getHeader(WEBSOCKET_PROTOCOL);
+ if (protocol != null) {
+ res.addHeader(WEBSOCKET_PROTOCOL, selectSubProtocol(protocol));
+ }
+ }
+
+ // Upgrade the connection and send the handshake response.
+ ChannelPipeline p = ctx.getChannel().getPipeline();
+ p.remove("aggregator");
+ p.replace("decoder", "wsdecoder", new WebSocket00FrameDecoder());
+
+ ctx.getChannel().write(res);
+
+ p.replace("encoder", "wsencoder", new WebSocket00FrameEncoder());
+ return;
+ }
+
+ /**
+ * Echo back the closing frame
+ *
+ * @param ctx
+ * Channel context
+ * @param frame
+ * Web Socket frame that was received
+ */
+ @Override
+ public void executeClosingHandshake(ChannelHandlerContext ctx, CloseWebSocketFrame frame) {
+ ctx.getChannel().write(frame);
+ }
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshaker10.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshaker10.java
new file mode 100644
index 0000000000..84467ab9e5
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshaker10.java
@@ -0,0 +1,167 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
+import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
+
+import java.security.NoSuchAlgorithmException;
+
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
+import org.jboss.netty.handler.codec.http.HttpRequest;
+import org.jboss.netty.handler.codec.http.HttpResponse;
+import org.jboss.netty.handler.codec.http.HttpResponseStatus;
+import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
+import org.jboss.netty.logging.InternalLogger;
+import org.jboss.netty.logging.InternalLoggerFactory;
+import org.jboss.netty.util.CharsetUtil;
+
+/**
+ *
+ * GET /chat HTTP/1.1
+ * Host: server.example.com
+ * Upgrade: websocket
+ * Connection: Upgrade
+ * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+ * Sec-WebSocket-Origin: http://example.com
+ * Sec-WebSocket-Protocol: chat, superchat
+ * Sec-WebSocket-Version: 8
+ *
+ *
+ *
+ * HTTP/1.1 101 Switching Protocols
+ * Upgrade: websocket
+ * Connection: Upgrade
+ * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
+ * Sec-WebSocket-Protocol: chat
+ *
+ *
+ * @param ctx
+ * Channel context
+ * @param req
+ * HTTP request
+ * @throws NoSuchAlgorithmException
+ */
+ @Override
+ public void executeOpeningHandshake(ChannelHandlerContext ctx, HttpRequest req) {
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Channel %s web socket spec version 10 handshake", ctx.getChannel().getId()));
+ }
+
+ HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, "Switching Protocols"));
+ this.setVersion(WebSocketSpecificationVersion.V10);
+
+ String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
+ if (key == null) {
+ res.setStatus(HttpResponseStatus.BAD_REQUEST);
+ return;
+ }
+ String acceptSeed = key + WEBSOCKET_08_ACCEPT_GUID;
+ byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
+ String accept = base64Encode(sha1);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("HyBi10 Server Handshake key: %s. Response: %s.", key, accept));
+ }
+
+ res.setStatus(new HttpResponseStatus(101, "Switching Protocols"));
+ res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
+ res.addHeader(Names.CONNECTION, Names.UPGRADE);
+ res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept);
+ String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
+ if (protocol != null) {
+ res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.selectSubProtocol(protocol));
+ }
+
+ ctx.getChannel().write(res);
+
+ // Upgrade the connection and send the handshake response.
+ ChannelPipeline p = ctx.getChannel().getPipeline();
+ p.remove("aggregator");
+ p.replace("decoder", "wsdecoder", new WebSocket08FrameDecoder(true, this.allowExtensions));
+ p.replace("encoder", "wsencoder", new WebSocket08FrameEncoder(false));
+
+ return;
+ }
+
+ /**
+ * Echo back the closing frame
+ *
+ * @param ctx
+ * Channel context
+ * @param frame
+ * Web Socket frame that was received
+ */
+ @Override
+ public void executeClosingHandshake(ChannelHandlerContext ctx, CloseWebSocketFrame frame) {
+ ctx.getChannel().write(frame);
+ }
+
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java
new file mode 100644
index 0000000000..ed5ff2ae52
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java
@@ -0,0 +1,108 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
+import org.jboss.netty.handler.codec.http.HttpRequest;
+import org.jboss.netty.handler.codec.http.HttpResponse;
+import org.jboss.netty.handler.codec.http.HttpResponseStatus;
+import org.jboss.netty.handler.codec.http.HttpVersion;
+import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
+
+/**
+ * Instances the appropriate handshake class to use for clients
+ *
+ * @author The Netty Project
+ */
+public class WebSocketServerHandshakerFactory {
+
+ private String webSocketURL;
+
+ private String subProtocols;
+
+ private boolean allowExtensions = false;
+
+ /**
+ * Constructor specifying the destination web socket location
+ *
+ * @param webSocketURL
+ * URL for web socket communications. e.g
+ * "ws://myhost.com/mypath". Subsequent web socket frames will be
+ * sent to this URL.
+ * @param subProtocols
+ * CSV of supported protocols. Null if sub protocols not
+ * supported.
+ * @param allowExtensions
+ * Allow extensions to be used in the reserved bits of the web
+ * socket frame
+ */
+ public WebSocketServerHandshakerFactory(String webSocketURL, String subProtocols, boolean allowExtensions) {
+ this.webSocketURL = webSocketURL;
+ this.subProtocols = subProtocols;
+ this.allowExtensions = allowExtensions;
+ return;
+ }
+
+ /**
+ * Instances a new handshaker
+ *
+ * @param webSocketURL
+ * URL for web socket communications. e.g
+ * "ws://myhost.com/mypath". Subsequent web socket frames will be
+ * sent to this URL.
+ * @param version
+ * Version of web socket specification to use to connect to the
+ * server
+ * @param subProtocol
+ * Sub protocol request sent to the server. Null if no
+ * sub-protocol support is required.
+ * @return A new WebSocketServerHandshaker for the requested web socket
+ * version. Null if web socket version is not supported.
+ */
+ public WebSocketServerHandshaker newHandshaker(ChannelHandlerContext ctx, HttpRequest req) {
+
+ String version = req.getHeader(Names.SEC_WEBSOCKET_VERSION);
+ if (version != null) {
+ if (version.equals("8")) {
+ // Version 8 of the wire protocol - assume version 10 of the
+ // specification.
+ return new WebSocketServerHandshaker10(webSocketURL, subProtocols, this.allowExtensions);
+ } else {
+ return null;
+ }
+ } else {
+ // Assume version 00 where version header was not specified
+ return new WebSocketServerHandshaker00(webSocketURL, subProtocols);
+ }
+ }
+
+ /**
+ * Return that we need cannot not support the web socket version
+ *
+ * @param ctx
+ * Context
+ */
+ public void sendUnsupportedWebSocketVersionResponse(ChannelHandlerContext ctx) {
+ HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(101,
+ "Switching Protocols"));
+ res.setStatus(HttpResponseStatus.UPGRADE_REQUIRED);
+ res.setHeader(Names.SEC_WEBSOCKET_VERSION, "8");
+ ctx.getChannel().write(res);
+ return;
+ }
+
+}
diff --git a/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketSpecificationVersion.java b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketSpecificationVersion.java
new file mode 100644
index 0000000000..c83779a7bf
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketSpecificationVersion.java
@@ -0,0 +1,45 @@
+/*
+ * 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.handler.codec.http.websocketx;
+
+/**
+ *