Change use of specification version to wire protocol version

This commit is contained in:
vibul 2011-12-15 14:34:20 +11:00
parent e479b3d6b2
commit cedbfba07d
21 changed files with 851 additions and 784 deletions

View File

@ -36,7 +36,7 @@
*
* <p>08. Go to test suite directory: <tt>cd testsuite/websockets</tt>
*
* <p>09. Edit <tt>fuzzing_clinet_spec.json</tt> and set the version to 10 or 17.
* <p>09. Edit <tt>fuzzing_clinet_spec.json</tt> and set the hybi specification version to 10 or 17 (RFC 6455).
* <code>
* {
* "options": {"failByDrop": false},

View File

@ -28,7 +28,7 @@ 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;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketVersion;
/**
* A HTTP client demo app
@ -59,7 +59,7 @@ public class App {
// If you change it to V00, ping is not supported and remember to change HttpResponseDecoder to
// WebSocketHttpResponseDecoder in the pipeline.
WebSocketClient client = factory.newClient(new URI("ws://localhost:8080/websocket"),
WebSocketSpecificationVersion.V17, callbackHandler);
WebSocketVersion.V13, callbackHandler);
// Connect
System.out.println("WebSocket Client connecting");

View File

@ -29,7 +29,7 @@ import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.http.HttpRequestEncoder;
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketSpecificationVersion;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketVersion;
import java.net.URI;
import java.util.concurrent.Executors;
@ -57,7 +57,7 @@ public class WebSocketClientFactory {
* @return A WebSocket client. Call {@link WebSocketClient#connect()} to connect.
*/
public WebSocketClient newClient(final URI url,
final WebSocketSpecificationVersion version,
final WebSocketVersion version,
final WebSocketCallback callback) {
ClientBootstrap bootstrap = new ClientBootstrap(socketChannelFactory);

View File

@ -37,7 +37,7 @@ import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketSpecificationVersion;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketVersion;
import org.jboss.netty.util.CharsetUtil;
/**
@ -53,9 +53,9 @@ public class WebSocketClientHandler extends SimpleChannelUpstreamHandler impleme
private final WebSocketCallback callback;
private Channel channel;
private WebSocketClientHandshaker handshaker = null;
private final WebSocketSpecificationVersion version;
private final WebSocketVersion version;
public WebSocketClientHandler(ClientBootstrap bootstrap, URI url, WebSocketSpecificationVersion version, WebSocketCallback callback) {
public WebSocketClientHandler(ClientBootstrap bootstrap, URI url, WebSocketVersion version, WebSocketCallback callback) {
this.bootstrap = bootstrap;
this.url = url;
this.version = version;

View File

@ -33,7 +33,7 @@ public abstract class WebSocketClientHandshaker {
private URI webSocketURL;
private WebSocketSpecificationVersion version = WebSocketSpecificationVersion.UNKNOWN;
private WebSocketVersion version = WebSocketVersion.UNKNOWN;
private boolean openingHandshakeCompleted = false;
@ -47,7 +47,7 @@ public abstract class WebSocketClientHandshaker {
* @param version
* @param subProtocol
*/
public WebSocketClientHandshaker(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol) {
public WebSocketClientHandshaker(URI webSocketURL, WebSocketVersion version, String subProtocol) {
this.webSocketURL = webSocketURL;
this.version = version;
this.subProtocolRequest = subProtocol;
@ -67,11 +67,11 @@ public abstract class WebSocketClientHandshaker {
/**
* Version of the web socket specification that is being used
*/
public WebSocketSpecificationVersion getVersion() {
public WebSocketVersion getVersion() {
return version;
}
protected void setVersion(WebSocketSpecificationVersion version) {
protected void setVersion(WebSocketVersion version) {
this.version = version;
}

View File

@ -61,7 +61,7 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
* @param subProtocol
* Sub protocol request sent to the server.
*/
public WebSocketClientHandshaker00(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol) {
public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subProtocol) {
super(webSocketURL, version, subProtocol);
}

View File

@ -0,0 +1,202 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
import org.jboss.netty.channel.Channel;
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.HttpRequestEncoder;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
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;
/**
* <p>
* Performs client side opening and closing handshakes for web socket
* specification version <a
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
* </p>
*/
public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
private static final InternalLogger logger = InternalLoggerFactory
.getInstance(WebSocketClientHandshaker08.class);
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private String expectedChallengeResponseString = null;
private static final String protocol = null;
private boolean allowExtensions = false;
/**
* Constructor specifying the destination web socket location and version to
* initiate
*
* @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.
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
*/
public WebSocketClientHandshaker08(URI webSocketURL,
WebSocketVersion version, String subProtocol,
boolean allowExtensions) {
super(webSocketURL, version, subProtocol);
this.allowExtensions = allowExtensions;
}
/**
* /**
* <p>
* Sends the opening request to the server:
* </p>
*
* <pre>
* 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
* </pre>
*
* @param channel
* Channel into which we can write our request
*/
@Override
public void performOpeningHandshake(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("WS Version 08 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);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder",
new WebSocket08FrameEncoder(true));
}
/**
* <p>
* Process server response:
* </p>
*
* <pre>
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat
* </pre>
*
* @param channel
* Channel
* @param response
* HTTP response returned from the server for the request sent by
* beginOpeningHandshake00().
* @throws WebSocketHandshakeException
*/
@Override
public void performClosingHandshake(Channel channel, 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));
}
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder",
new WebSocket08FrameDecoder(false, this.allowExtensions));
this.setOpenningHandshakeCompleted(true);
}
}

View File

@ -1,183 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
import org.jboss.netty.channel.Channel;
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.HttpRequestEncoder;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
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;
/**
* <p>
* Performs client side opening and closing handshakes for web socket
* specification version <a
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
* </p>
*/
public class WebSocketClientHandshaker10 extends WebSocketClientHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker10.class);
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private String expectedChallengeResponseString = null;
private static final String protocol = null;
private boolean allowExtensions = false;
/**
* Constructor specifying the destination web socket location and version to
* initiate
*
* @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.
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
*/
public WebSocketClientHandshaker10(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol, boolean allowExtensions) {
super(webSocketURL, version, subProtocol);
this.allowExtensions = allowExtensions;
}
/**
* /**
* <p>
* Sends the opening request to the server:
* </p>
*
* <pre>
* 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
* </pre>
*
* @param channel
* Channel into which we can write our request
*/
@Override
public void performOpeningHandshake(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);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket08FrameEncoder(true));
}
/**
* <p>
* Process server response:
* </p>
*
* <pre>
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat
* </pre>
*
* @param channel
* Channel
* @param response
* HTTP response returned from the server for the request sent by
* beginOpeningHandshake00().
* @throws WebSocketHandshakeException
*/
@Override
public void performClosingHandshake(Channel channel, 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));
}
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket08FrameDecoder(false, this.allowExtensions));
this.setOpenningHandshakeCompleted(true);
}
}

View File

@ -0,0 +1,202 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
import org.jboss.netty.channel.Channel;
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.HttpRequestEncoder;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
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;
/**
* <p>
* Performs client side opening and closing handshakes for <a
* href="http://tools.ietf.org/html/rfc6455">RFC 6455</a>. This was originally
* <a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
* >draft-ietf-hybi-thewebsocketprotocol- 17</a>
* </p>
*/
public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
private static final InternalLogger logger = InternalLoggerFactory
.getInstance(WebSocketClientHandshaker13.class);
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private String expectedChallengeResponseString = null;
private static final String protocol = null;
private boolean allowExtensions = false;
/**
* Constructor specifying the destination web socket location and version to
* initiate
*
* @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.
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
*/
public WebSocketClientHandshaker13(URI webSocketURL,
WebSocketVersion version, String subProtocol,
boolean allowExtensions) {
super(webSocketURL, version, subProtocol);
this.allowExtensions = allowExtensions;
}
/**
* /**
* <p>
* Sends the opening request to the server:
* </p>
*
* <pre>
* 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: 13
* </pre>
*
* @param channel
* Channel into which we can write our request
*/
@Override
public void performOpeningHandshake(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(
"WS Version 13 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, "13");
channel.write(request);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder",
new WebSocket13FrameEncoder(true));
}
/**
* <p>
* Process server response:
* </p>
*
* <pre>
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat
* </pre>
*
* @param channel
* Channel
* @param response
* HTTP response returned from the server for the request sent by
* beginOpeningHandshake00().
* @throws WebSocketHandshakeException
*/
@Override
public void performClosingHandshake(Channel channel, 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));
}
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder",
new WebSocket13FrameDecoder(false, this.allowExtensions));
this.setOpenningHandshakeCompleted(true);
}
}

View File

@ -1,183 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
import org.jboss.netty.channel.Channel;
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.HttpRequestEncoder;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
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;
/**
* <p>
* Performs client side opening and closing handshakes for web socket
* specification version <a
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
* >draft-ietf-hybi-thewebsocketprotocol- 17</a>
* </p>
*/
public class WebSocketClientHandshaker17 extends WebSocketClientHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker17.class);
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private String expectedChallengeResponseString = null;
private static final String protocol = null;
private boolean allowExtensions = false;
/**
* Constructor specifying the destination web socket location and version to
* initiate
*
* @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.
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
*/
public WebSocketClientHandshaker17(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol, boolean allowExtensions) {
super(webSocketURL, version, subProtocol);
this.allowExtensions = allowExtensions;
}
/**
* /**
* <p>
* Sends the opening request to the server:
* </p>
*
* <pre>
* 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: 13
* </pre>
*
* @param channel
* Channel into which we can write our request
*/
@Override
public void performOpeningHandshake(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("HyBi17 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, "13");
channel.write(request);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket13FrameEncoder(true));
}
/**
* <p>
* Process server response:
* </p>
*
* <pre>
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat
* </pre>
*
* @param channel
* Channel
* @param response
* HTTP response returned from the server for the request sent by
* beginOpeningHandshake00().
* @throws WebSocketHandshakeException
*/
@Override
public void performClosingHandshake(Channel channel, 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));
}
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket13FrameDecoder(false, this.allowExtensions));
this.setOpenningHandshakeCompleted(true);
}
}

View File

@ -40,14 +40,14 @@ public class WebSocketClientHandshakerFactory {
* socket frame
* @throws WebSocketHandshakeException
*/
public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol, boolean allowExtensions) throws WebSocketHandshakeException {
if (version == WebSocketSpecificationVersion.V17) {
return new WebSocketClientHandshaker17(webSocketURL, version, subProtocol, allowExtensions);
public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketVersion version, String subProtocol, boolean allowExtensions) throws WebSocketHandshakeException {
if (version == WebSocketVersion.V13) {
return new WebSocketClientHandshaker13(webSocketURL, version, subProtocol, allowExtensions);
}
if (version == WebSocketSpecificationVersion.V10) {
return new WebSocketClientHandshaker10(webSocketURL, version, subProtocol, allowExtensions);
if (version == WebSocketVersion.V08) {
return new WebSocketClientHandshaker08(webSocketURL, version, subProtocol, allowExtensions);
}
if (version == WebSocketSpecificationVersion.V00) {
if (version == WebSocketVersion.V00) {
return new WebSocketClientHandshaker00(webSocketURL, version, subProtocol);
}

View File

@ -33,7 +33,7 @@ public abstract class WebSocketServerHandshaker {
private String[] subProtocolsArray = null;
private WebSocketSpecificationVersion version = WebSocketSpecificationVersion.UNKNOWN;
private WebSocketVersion version = WebSocketVersion.UNKNOWN;
/**
* Constructor specifying the destination web socket location
@ -83,11 +83,11 @@ public abstract class WebSocketServerHandshaker {
/**
* Returns the version of the specification being supported
*/
public WebSocketSpecificationVersion getVersion() {
public WebSocketVersion getVersion() {
return version;
}
public void setVersion(WebSocketSpecificationVersion version) {
public void setVersion(WebSocketVersion version) {
this.version = version;
}

View File

@ -129,7 +129,7 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Channel %s web socket spec version 00 handshake", channel.getId()));
}
this.setVersion(WebSocketSpecificationVersion.V00);
this.setVersion(WebSocketVersion.V00);
// Serve the WebSocket handshake request.
if (!Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION)) || !WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) {

View File

@ -0,0 +1,180 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package 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.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
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;
/**
* <p>
* Performs server side opening and closing handshakes for web socket
* specification version <a
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
* </p>
*/
public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
private static final InternalLogger logger = InternalLoggerFactory
.getInstance(WebSocketServerHandshaker08.class);
public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
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
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
*/
public WebSocketServerHandshaker08(String webSocketURL,
String subProtocols, boolean allowExtensions) {
super(webSocketURL, subProtocols);
this.allowExtensions = allowExtensions;
}
/**
* <p>
* Handle the web socket handshake for the web socket specification <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-08">HyBi
* version 8 to 10</a>. Version 8, 9 and 10 share the same wire protocol.
* </p>
*
* <p>
* Browser request to the server:
* </p>
*
* <pre>
* 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
* </pre>
*
* <p>
* Server response:
* </p>
*
* <pre>
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat
* </pre>
*
* @param channel
* Channel
* @param req
* HTTP request
* @throws NoSuchAlgorithmException
*/
@Override
public void performOpeningHandshake(Channel channel, HttpRequest req) {
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"Channel %s WS version 08 handshake",
channel.getId()));
}
HttpResponse res = new DefaultHttpResponse(HTTP_1_1,
new HttpResponseStatus(101, "Switching Protocols"));
this.setVersion(WebSocketVersion.V08);
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(
"WS Version 08 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));
}
channel.write(res);
// Upgrade the connection and send the handshake response.
ChannelPipeline p = channel.getPipeline();
p.remove(HttpChunkAggregator.class);
p.replace(HttpRequestDecoder.class, "wsdecoder",
new WebSocket08FrameDecoder(true, this.allowExtensions));
p.replace(HttpResponseEncoder.class, "wsencoder",
new WebSocket08FrameEncoder(false));
}
/**
* Echo back the closing frame and close the connection
*
* @param channel
* Channel
* @param frame
* Web Socket frame that was received
*/
@Override
public void performClosingHandshake(Channel channel,
CloseWebSocketFrame frame) {
ChannelFuture f = channel.write(frame);
f.addListener(ChannelFutureListener.CLOSE);
}
}

View File

@ -1,169 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package 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.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
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;
/**
* <p>
* Performs server side opening and closing handshakes for web socket
* specification version <a
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
* </p>
*/
public class WebSocketServerHandshaker10 extends WebSocketServerHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker10.class);
public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
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
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
*/
public WebSocketServerHandshaker10(String webSocketURL, String subProtocols, boolean allowExtensions) {
super(webSocketURL, subProtocols);
this.allowExtensions = allowExtensions;
}
/**
* <p>
* Handle the web socket handshake for the web socket specification <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-08">HyBi
* version 8 to 10</a>. Version 8, 9 and 10 share the same wire protocol.
* </p>
*
* <p>
* Browser request to the server:
* </p>
*
* <pre>
* 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
* </pre>
*
* <p>
* Server response:
* </p>
*
* <pre>
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat
* </pre>
*
* @param channel
* Channel
* @param req
* HTTP request
* @throws NoSuchAlgorithmException
*/
@Override
public void performOpeningHandshake(Channel channel, HttpRequest req) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Channel %s web socket spec version 10 handshake", channel.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));
}
channel.write(res);
// Upgrade the connection and send the handshake response.
ChannelPipeline p = channel.getPipeline();
p.remove(HttpChunkAggregator.class);
p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket08FrameDecoder(true, this.allowExtensions));
p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket08FrameEncoder(false));
}
/**
* Echo back the closing frame and close the connection
*
* @param channel
* Channel
* @param frame
* Web Socket frame that was received
*/
@Override
public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) {
ChannelFuture f = channel.write(frame);
f.addListener(ChannelFutureListener.CLOSE);
}
}

View File

@ -0,0 +1,180 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package 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.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
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;
/**
* <p>
* Performs server side opening and closing handshakes for <a
* href="http://tools.ietf.org/html/rfc6455">RFC 6455</a>. This was originally
* <a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
* >draft-ietf-hybi-thewebsocketprotocol- 17</a>
* </p>
*/
public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
private static final InternalLogger logger = InternalLoggerFactory
.getInstance(WebSocketServerHandshaker13.class);
public static final String WEBSOCKET_17_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
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
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
*/
public WebSocketServerHandshaker13(String webSocketURL,
String subProtocols, boolean allowExtensions) {
super(webSocketURL, subProtocols);
this.allowExtensions = allowExtensions;
}
/**
* <p>
* Handle the web socket handshake for the web socket specification <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi
* versions 13-17</a>. Versions 13-17 share the same wire protocol.
* </p>
*
* <p>
* Browser request to the server:
* </p>
*
* <pre>
* 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: 13
* </pre>
*
* <p>
* Server response:
* </p>
*
* <pre>
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat
* </pre>
*
* @param channel
* Channel
* @param req
* HTTP request
* @throws NoSuchAlgorithmException
*/
@Override
public void performOpeningHandshake(Channel channel, HttpRequest req) {
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"Channel %s WS version 13 handshake",
channel.getId()));
}
HttpResponse res = new DefaultHttpResponse(HTTP_1_1,
new HttpResponseStatus(101, "Switching Protocols"));
this.setVersion(WebSocketVersion.V13);
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
if (key == null) {
res.setStatus(HttpResponseStatus.BAD_REQUEST);
return;
}
String acceptSeed = key + WEBSOCKET_17_ACCEPT_GUID;
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
String accept = base64Encode(sha1);
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"WS Version 13 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));
}
channel.write(res);
// Upgrade the connection and send the handshake response.
ChannelPipeline p = channel.getPipeline();
p.remove(HttpChunkAggregator.class);
p.replace(HttpRequestDecoder.class, "wsdecoder",
new WebSocket13FrameDecoder(true, this.allowExtensions));
p.replace(HttpResponseEncoder.class, "wsencoder",
new WebSocket13FrameEncoder(false));
}
/**
* Echo back the closing frame and close the connection
*
* @param channel
* Channel
* @param frame
* Web Socket frame that was received
*/
@Override
public void performClosingHandshake(Channel channel,
CloseWebSocketFrame frame) {
ChannelFuture f = channel.write(frame);
f.addListener(ChannelFutureListener.CLOSE);
}
}

View File

@ -1,169 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package 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.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
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;
/**
* <p>
* Performs server side opening and closing handshakes for web socket
* specification version <a
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
* >draft-ietf-hybi-thewebsocketprotocol- 17</a>
* </p>
*/
public class WebSocketServerHandshaker17 extends WebSocketServerHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker17.class);
public static final String WEBSOCKET_17_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
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
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
*/
public WebSocketServerHandshaker17(String webSocketURL, String subProtocols, boolean allowExtensions) {
super(webSocketURL, subProtocols);
this.allowExtensions = allowExtensions;
}
/**
* <p>
* Handle the web socket handshake for the web socket specification <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi
* versions 13-17</a>. Versions 13-17 share the same wire protocol.
* </p>
*
* <p>
* Browser request to the server:
* </p>
*
* <pre>
* 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: 13
* </pre>
*
* <p>
* Server response:
* </p>
*
* <pre>
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat
* </pre>
*
* @param channel
* Channel
* @param req
* HTTP request
* @throws NoSuchAlgorithmException
*/
@Override
public void performOpeningHandshake(Channel channel, HttpRequest req) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Channel %s web socket spec version 17 handshake", channel.getId()));
}
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, "Switching Protocols"));
this.setVersion(WebSocketSpecificationVersion.V17);
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
if (key == null) {
res.setStatus(HttpResponseStatus.BAD_REQUEST);
return;
}
String acceptSeed = key + WEBSOCKET_17_ACCEPT_GUID;
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
String accept = base64Encode(sha1);
if (logger.isDebugEnabled()) {
logger.debug(String.format("HyBi17 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));
}
channel.write(res);
// Upgrade the connection and send the handshake response.
ChannelPipeline p = channel.getPipeline();
p.remove(HttpChunkAggregator.class);
p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket13FrameDecoder(true, this.allowExtensions));
p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false));
}
/**
* Echo back the closing frame and close the connection
*
* @param channel
* Channel
* @param frame
* Web Socket frame that was received
*/
@Override
public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) {
ChannelFuture f = channel.write(frame);
f.addListener(ChannelFutureListener.CLOSE);
}
}

View File

@ -64,14 +64,14 @@ public class WebSocketServerHandshakerFactory {
String version = req.getHeader(Names.SEC_WEBSOCKET_VERSION);
if (version != null) {
if (version.equals("13")) {
if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) {
// Version 13 of the wire protocol - assume version 17 of the
// specification.
return new WebSocketServerHandshaker17(webSocketURL, subProtocols, this.allowExtensions);
} else if (version.equals("8")) {
return new WebSocketServerHandshaker13(webSocketURL, subProtocols, this.allowExtensions);
} else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) {
// Version 8 of the wire protocol - assume version 10 of the
// specification.
return new WebSocketServerHandshaker10(webSocketURL, subProtocols, this.allowExtensions);
return new WebSocketServerHandshaker08(webSocketURL, subProtocols, this.allowExtensions);
} else {
return null;
}
@ -91,7 +91,7 @@ public class WebSocketServerHandshakerFactory {
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(101,
"Switching Protocols"));
res.setStatus(HttpResponseStatus.UPGRADE_REQUIRED);
res.setHeader(Names.SEC_WEBSOCKET_VERSION, "13");
res.setHeader(Names.SEC_WEBSOCKET_VERSION, WebSocketVersion.V13.toHttpHeaderValue());
channel.write(res);
}

View File

@ -1,51 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
/**
* <p>
* Versions of the web socket specification.
* </p>
* <p>
* A specification is tied to one wire protocol version but a protocol version
* may have use by more than 1 version of the specification.
* </p>
*/
public enum WebSocketSpecificationVersion {
UNKNOWN,
/**
* <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00"
* >draft-ietf-hybi-thewebsocketprotocol- 00</a>.
*/
V00,
/**
* <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
*/
V10,
/**
* <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
* >draft-ietf-hybi-thewebsocketprotocol- 17</a>
*/
V17
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
/**
* <p>
* Version of the web socket wire protocol.
* </p>
*/
public enum WebSocketVersion {
UNKNOWN,
/**
* <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00"
* >draft-ietf-hybi-thewebsocketprotocol- 00</a>.
*/
V00,
/**
* <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
*/
V08,
/**
* <a href="http://tools.ietf.org/html/rfc6455 ">RFC 6455</a>. This was
* originally <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
* >draft-ietf-hybi-thewebsocketprotocol- 17</a>
*/
V13;
public String toHttpHeaderValue() {
if (this == V00) {
return "0";
} else if (this == V08) {
return "8";
} else if (this == V13) {
return "13";
}
throw new IllegalArgumentException(this.toString() + " cannot be converted to a string.");
}
}

View File

@ -23,7 +23,7 @@
* <ul>
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">draft-ietf-hybi-thewebsocketprotocol-00</a></li>
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10">draft-ietf-hybi-thewebsocketprotocol-10</a></li>
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">draft-ietf-hybi-thewebsocketprotocol-17</a></li>
* <li><a href="http://tools.ietf.org/html/rfc6455 ">RFC 6455</a> (originally <a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">draft-ietf-hybi-thewebsocketprotocol-17</a>)</li>
* </ul>
* </p>
* <p>