+ * 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.getType() == WebSocketFrameType.TEXT) {
+ // 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.getType() == WebSocketFrameType.CLOSE) {
+ // 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..6579bd03d2
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java
@@ -0,0 +1,293 @@
+// (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.channel.Channel;
+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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Decodes a web socket frame from wire protocol version 8 format. This code was
+ * originally taken from webbit and modified.
+ *
+ * @author https://github.com/joewalnes/webbit
+ * @author Vibul Imtarnasan
+ */
+public class WebSocket08FrameDecoder extends ReplayingDecoder
+ * Encodes a web socket frame into wire protocol version 8 format. This code was
+ * originally taken from webbit and modified.
+ *
+ * Currently fragmentation is not supported. Data is always sent in 1 frame.
+ *
+ * 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.
+ *
+ * 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..5ce6547a02
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketClientHandshaker10.java
@@ -0,0 +1,188 @@
+/*
+ * 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 = Base64.encode(nonce);
+
+ String acceptSeed = key + MAGIC_GUID;
+ byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
+ this.expectedChallengeResponseString = Base64.encode(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.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..739ad48560
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ * @throws WebSocketHandshakeException
+ */
+ public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketSpecificationVersion version,
+ String subProtocol) throws WebSocketHandshakeException {
+ if (version == WebSocketSpecificationVersion.V10) {
+ return new WebSocketClientHandshaker10(webSocketURL, version, subProtocol);
+ }
+ 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..3893a701dd
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketFrame.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.ChannelBuffer;
+
+/**
+ * Base class for web socket frames
+ *
+ * @author The Netty Project
+ */
+public abstract class WebSocketFrame {
+
+ /**
+ * Contents of this frame
+ */
+ private ChannelBuffer binaryData;
+
+ /**
+ * Returns the type of this frame.
+ */
+ public abstract WebSocketFrameType getType();
+
+ /**
+ * Returns binary data
+ */
+ public ChannelBuffer getBinaryData() {
+ return binaryData;
+ }
+
+ /**
+ * Sets the binary data for this frame
+ */
+ public void setBinaryData(ChannelBuffer binaryData) {
+ this.binaryData = binaryData;
+ }
+
+}
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..e0650f8621
--- /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
+}
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..ace5ecafab
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java
@@ -0,0 +1,175 @@
+/*
+ * 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.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.http.HttpRequest;
+
+/**
+ * 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");
+ }
+ }
+
+ /**
+ * 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..fed122013c
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshaker10.java
@@ -0,0 +1,161 @@
+/*
+ * 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 = Base64.encode(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));
+ 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..64d83be35b
--- /dev/null
+++ b/src/main/java/org/jboss/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+ /**
+ * 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 WebSocketServerHandshakerFactory(String webSocketURL, String subProtocols) {
+ this.webSocketURL = webSocketURL;
+ this.subProtocols = subProtocols;
+ 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);
+ } 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;
+
+/**
+ *