Support websocket client custom headers. See pull #123

This commit is contained in:
vibul 2011-12-15 15:09:21 +11:00
parent cedbfba07d
commit f5cc1e02fa
9 changed files with 563 additions and 527 deletions

View File

@ -18,6 +18,7 @@ package org.jboss.netty.example.http.websocketx.client;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -55,31 +56,34 @@ public class App {
MyCallbackHandler callbackHandler = new MyCallbackHandler();
WebSocketClientFactory factory = new WebSocketClientFactory();
// Connect with spec version 17. You can change it to V10 or V00.
// 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"),
WebSocketVersion.V13, callbackHandler);
HashMap<String, String> customHeaders = new HashMap<String, String>();
customHeaders.put("MyHeader", "MyValue");
// Connect with V13 (RFC 6455). You can change it to V08 or V00.
// 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"), WebSocketVersion.V13,
callbackHandler, customHeaders);
// Connect
System.out.println("WebSocket Client connecting");
client.connect().awaitUninterruptibly();
System.out.println("WebSocket Client connecting");
client.connect().awaitUninterruptibly();
Thread.sleep(200);
// Send 10 messages and wait for responses
System.out.println("WebSocket Client sending message");
System.out.println("WebSocket Client sending message");
for (int i = 0; i < 10; i++) {
client.send(new TextWebSocketFrame("Message #" + i));
}
Thread.sleep(1000);
// Ping
System.out.println("WebSocket Client sending ping");
System.out.println("WebSocket Client sending ping");
client.send(new PingWebSocketFrame(ChannelBuffers.copiedBuffer(new byte[] { 1, 2, 3, 4, 5, 6 })));
Thread.sleep(1000);
// Close
System.out.println("WebSocket Client sending close");
System.out.println("WebSocket Client sending close");
client.send(new CloseWebSocketFrame());
Thread.sleep(1000);

View File

@ -29,26 +29,26 @@ import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
*/
public interface WebSocketClient {
/**
* Connect to server Host and port is setup by the factory.
*
* @return Connect future. Fires when connected.
*/
ChannelFuture connect();
/**
* Connect to server Host and port is setup by the factory.
*
* @return Connect future. Fires when connected.
*/
ChannelFuture connect();
/**
* Disconnect from the server
*
* @return Disconnect future. Fires when disconnected.
*/
ChannelFuture disconnect();
/**
* Disconnect from the server
*
* @return Disconnect future. Fires when disconnected.
*/
ChannelFuture disconnect();
/**
* Send data to server
*
* @param frame
* Data for sending
* @return Write future. Will fire when the data is sent.
*/
ChannelFuture send(WebSocketFrame frame);
/**
* Send data to server
*
* @param frame
* Data for sending
* @return Write future. Will fire when the data is sent.
*/
ChannelFuture send(WebSocketFrame frame);
}

View File

@ -32,57 +32,62 @@ import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketVersion;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.Executors;
/**
* Copied from https://github.com/cgbystrom/netty-tools
*
* A factory for creating WebSocket clients. The entry point for creating and connecting a client. Can and should be
* used to create multiple instances.
* A factory for creating WebSocket clients. The entry point for creating and
* connecting a client. Can and should be used to create multiple instances.
*/
public class WebSocketClientFactory {
private final NioClientSocketChannelFactory socketChannelFactory = new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool());
private final NioClientSocketChannelFactory socketChannelFactory = new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool());
/**
* Create a new WebSocket client
*
* @param url
* URL to connect to.
* @param version
* Web Socket version to support
* @param callback
* Callback interface to receive events
* @return A WebSocket client. Call {@link WebSocketClient#connect()} to connect.
*/
public WebSocketClient newClient(final URI url,
final WebSocketVersion version,
final WebSocketCallback callback) {
ClientBootstrap bootstrap = new ClientBootstrap(socketChannelFactory);
/**
* Create a new WebSocket client
*
* @param url
* URL to connect to.
* @param version
* Web Socket version to support
* @param callback
* Callback interface to receive events
* @param customHeaders
* Map of custom headers to add to the client request
* @return A WebSocket client. Call {@link WebSocketClient#connect()} to
* connect.
*/
public WebSocketClient newClient(final URI url, final WebSocketVersion version, final WebSocketCallback callback,
final Map<String, String> customHeaders) {
ClientBootstrap bootstrap = new ClientBootstrap(socketChannelFactory);
String protocol = url.getScheme();
if (!protocol.equals("ws") && !protocol.equals("wss")) {
throw new IllegalArgumentException("Unsupported protocol: " + protocol);
}
String protocol = url.getScheme();
if (!protocol.equals("ws") && !protocol.equals("wss")) {
throw new IllegalArgumentException("Unsupported protocol: " + protocol);
}
final WebSocketClientHandler clientHandler = new WebSocketClientHandler(bootstrap, url, version, callback);
final WebSocketClientHandler clientHandler = new WebSocketClientHandler(bootstrap, url, version, callback,
customHeaders);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
// If you wish to support HyBi V00, you need to use WebSocketHttpResponseDecoder instead for
// HttpResponseDecoder.
pipeline.addLast("decoder", new HttpResponseDecoder());
pipeline.addLast("encoder", new HttpRequestEncoder());
pipeline.addLast("ws-handler", clientHandler);
return pipeline;
}
});
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
return clientHandler;
}
// If you wish to support HyBi V00, you need to use
// WebSocketHttpResponseDecoder instead for
// HttpResponseDecoder.
pipeline.addLast("decoder", new HttpResponseDecoder());
pipeline.addLast("encoder", new HttpRequestEncoder());
pipeline.addLast("ws-handler", clientHandler);
return pipeline;
}
});
return clientHandler;
}
}

View File

@ -24,6 +24,7 @@ package org.jboss.netty.example.http.websocketx.client;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Map;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
@ -43,79 +44,85 @@ import org.jboss.netty.util.CharsetUtil;
/**
* Copied from https://github.com/cgbystrom/netty-tools
*
* Handles socket communication for a connected WebSocket client Not intended for end-users. Please use
* {@link WebSocketClient} or {@link WebSocketCallback} for controlling your client.
* Handles socket communication for a connected WebSocket client Not intended
* for end-users. Please use {@link WebSocketClient} or
* {@link WebSocketCallback} for controlling your client.
*/
public class WebSocketClientHandler extends SimpleChannelUpstreamHandler implements WebSocketClient {
private final ClientBootstrap bootstrap;
private URI url;
private final WebSocketCallback callback;
private Channel channel;
private WebSocketClientHandshaker handshaker = null;
private final WebSocketVersion version;
private final ClientBootstrap bootstrap;
private URI url;
private final WebSocketCallback callback;
private Channel channel;
private WebSocketClientHandshaker handshaker = null;
private final WebSocketVersion version;
private Map<String, String> customHeaders = null;
public WebSocketClientHandler(ClientBootstrap bootstrap, URI url, WebSocketVersion version, WebSocketCallback callback) {
this.bootstrap = bootstrap;
this.url = url;
this.version = version;
this.callback = callback;
}
public WebSocketClientHandler(ClientBootstrap bootstrap, URI url, WebSocketVersion version,
WebSocketCallback callback, Map<String, String> customHeaders) {
this.bootstrap = bootstrap;
this.url = url;
this.version = version;
this.callback = callback;
this.customHeaders = customHeaders;
}
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
channel = e.getChannel();
this.handshaker = new WebSocketClientHandshakerFactory().newHandshaker(url, version, null, false);
handshaker.performOpeningHandshake(channel);
}
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
channel = e.getChannel();
this.handshaker = new WebSocketClientHandshakerFactory()
.newHandshaker(url, version, null, false, customHeaders);
handshaker.performOpeningHandshake(channel);
}
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
callback.onDisconnect(this);
}
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
callback.onDisconnect(this);
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
if (!handshaker.isOpeningHandshakeCompleted()) {
handshaker.performClosingHandshake(ctx.getChannel(), (HttpResponse) e.getMessage());
callback.onConnect(this);
return;
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
if (!handshaker.isOpeningHandshakeCompleted()) {
handshaker.performClosingHandshake(ctx.getChannel(), (HttpResponse) e.getMessage());
callback.onConnect(this);
return;
}
if (e.getMessage() instanceof HttpResponse) {
HttpResponse response = (HttpResponse) e.getMessage();
throw new WebSocketException("Unexpected HttpResponse (status=" + response.getStatus() + ", content="
+ response.getContent().toString(CharsetUtil.UTF_8) + ")");
}
if (e.getMessage() instanceof HttpResponse) {
HttpResponse response = (HttpResponse) e.getMessage();
throw new WebSocketException("Unexpected HttpResponse (status=" + response.getStatus() + ", content="
+ response.getContent().toString(CharsetUtil.UTF_8) + ")");
}
WebSocketFrame frame = (WebSocketFrame) e.getMessage();
callback.onMessage(this, frame);
}
WebSocketFrame frame = (WebSocketFrame) e.getMessage();
callback.onMessage(this, frame);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
final Throwable t = e.getCause();
callback.onError(t);
e.getChannel().close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
final Throwable t = e.getCause();
callback.onError(t);
e.getChannel().close();
}
public ChannelFuture connect() {
return bootstrap.connect(new InetSocketAddress(url.getHost(), url.getPort()));
}
public ChannelFuture connect() {
return bootstrap.connect(new InetSocketAddress(url.getHost(), url.getPort()));
}
public ChannelFuture disconnect() {
return channel.close();
}
public ChannelFuture disconnect() {
return channel.close();
}
public ChannelFuture send(WebSocketFrame frame) {
return channel.write(frame);
}
public ChannelFuture send(WebSocketFrame frame) {
return channel.write(frame);
}
public URI getUrl() {
return url;
}
public URI getUrl() {
return url;
}
public void setUrl(URI url) {
this.url = url;
}
public void setUrl(URI url) {
this.url = url;
}
}

View File

@ -18,6 +18,7 @@ package org.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
@ -31,174 +32,188 @@ import org.jboss.netty.util.CharsetUtil;
*/
public abstract class WebSocketClientHandshaker {
private URI webSocketURL;
private URI webSocketURL;
private WebSocketVersion version = WebSocketVersion.UNKNOWN;
private WebSocketVersion version = WebSocketVersion.UNKNOWN;
private boolean openingHandshakeCompleted = false;
private boolean openingHandshakeCompleted = false;
private String subProtocolRequest = null;
private String subProtocolRequest = null;
private String subProtocolResponse = null;
private String subProtocolResponse = null;
/**
*
* @param webSocketURL
* @param version
* @param subProtocol
*/
public WebSocketClientHandshaker(URI webSocketURL, WebSocketVersion version, String subProtocol) {
this.webSocketURL = webSocketURL;
this.version = version;
this.subProtocolRequest = subProtocol;
}
protected Map<String, String> customHeaders = null;
/**
* Returns the URI to the web socket. e.g. "ws://myhost.com/path"
*/
public URI getWebSocketURL() {
return webSocketURL;
}
/**
* Base constructor
*
* @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 customHeaders
* Map of custom headers to add to the client request
*/
public WebSocketClientHandshaker(URI webSocketURL, WebSocketVersion version, String subProtocol,
Map<String, String> customHeaders) {
this.webSocketURL = webSocketURL;
this.version = version;
this.subProtocolRequest = subProtocol;
this.customHeaders = customHeaders;
}
protected void setWebSocketURL(URI webSocketURL) {
this.webSocketURL = webSocketURL;
}
/**
* Returns the URI to the web socket. e.g. "ws://myhost.com/path"
*/
public URI getWebSocketURL() {
return webSocketURL;
}
/**
* Version of the web socket specification that is being used
*/
public WebSocketVersion getVersion() {
return version;
}
protected void setWebSocketURL(URI webSocketURL) {
this.webSocketURL = webSocketURL;
}
protected void setVersion(WebSocketVersion version) {
this.version = version;
}
/**
* Version of the web socket specification that is being used
*/
public WebSocketVersion getVersion() {
return version;
}
/**
* Flag to indicate if the opening handshake is complete
*/
public boolean isOpeningHandshakeCompleted() {
return openingHandshakeCompleted;
}
protected void setVersion(WebSocketVersion version) {
this.version = version;
}
protected void setOpenningHandshakeCompleted(boolean openningHandshakeCompleted) {
this.openingHandshakeCompleted = openningHandshakeCompleted;
}
/**
* Flag to indicate if the opening handshake is complete
*/
public boolean isOpeningHandshakeCompleted() {
return openingHandshakeCompleted;
}
/**
* Returns the sub protocol request sent to the server as specified in the
* constructor
*/
public String getSubProtocolRequest() {
return subProtocolRequest;
}
protected void setOpenningHandshakeCompleted(boolean openningHandshakeCompleted) {
this.openingHandshakeCompleted = openningHandshakeCompleted;
}
protected void setSubProtocolRequest(String subProtocolRequest) {
this.subProtocolRequest = subProtocolRequest;
}
/**
* Returns the sub protocol request sent to the server as specified in the
* constructor
*/
public String getSubProtocolRequest() {
return subProtocolRequest;
}
/**
* Returns the sub protocol response and sent by the server. Only available
* after end of handshake.
*/
public String getSubProtocolResponse() {
return subProtocolResponse;
}
protected void setSubProtocolRequest(String subProtocolRequest) {
this.subProtocolRequest = subProtocolRequest;
}
protected void setSubProtocolResponse(String subProtocolResponse) {
this.subProtocolResponse = subProtocolResponse;
}
/**
* Returns the sub protocol response and sent by the server. Only available
* after end of handshake.
*/
public String getSubProtocolResponse() {
return subProtocolResponse;
}
/**
* Performs the opening handshake
*
* @param channel
* Channel
*/
public abstract void performOpeningHandshake(Channel channel);
protected void setSubProtocolResponse(String subProtocolResponse) {
this.subProtocolResponse = subProtocolResponse;
}
/**
* Performs the closing handshake
*
* @param channel
* Channel
* @param response
* HTTP response containing the closing handshake details
*/
public abstract void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException;
/**
* Performs the opening handshake
*
* @param channel
* Channel
*/
public abstract void performOpeningHandshake(Channel channel);
/**
* 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");
}
}
/**
* Performs the closing handshake
*
* @param channel
* Channel
* @param response
* HTTP response containing the closing handshake details
*/
public abstract void performClosingHandshake(Channel channel, HttpResponse response)
throws WebSocketHandshakeException;
/**
* Performs an SHA-1 hash
*
* @param bytes
* Data to hash
* @return Hashed data
*/
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");
}
}
/**
* 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");
}
}
/**
* Base 64 encoding
*
* @param bytes
* Bytes to encode
* @return encoded string
*/
protected String base64Encode(byte[] bytes) {
ChannelBuffer hashed = ChannelBuffers.wrappedBuffer(bytes);
return Base64.encode(hashed).toString(CharsetUtil.UTF_8);
}
/**
* Performs an SHA-1 hash
*
* @param bytes
* Data to hash
* @return Hashed data
*/
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");
}
}
/**
* Creates some random bytes
*
* @param size
* Number of random bytes to create
* @return random bytes
*/
protected byte[] createRandomBytes(int size) {
byte[] bytes = new byte[size];
/**
* Base 64 encoding
*
* @param bytes
* Bytes to encode
* @return encoded string
*/
protected String base64Encode(byte[] bytes) {
ChannelBuffer hashed = ChannelBuffers.wrappedBuffer(bytes);
return Base64.encode(hashed).toString(CharsetUtil.UTF_8);
}
for (int i = 0; i < size; i++) {
bytes[i] = (byte) createRandomNumber(0, 255);
}
/**
* Creates some random bytes
*
* @param size
* Number of random bytes to create
* @return random bytes
*/
protected byte[] createRandomBytes(int size) {
byte[] bytes = new byte[size];
return bytes;
}
for (int i = 0; i < size; i++) {
bytes[i] = (byte) createRandomNumber(0, 255);
}
/**
* Generates a random number
*
* @param min
* Minimum value
* @param max
* Maximum value
* @return Random number
*/
protected int createRandomNumber(int min, int max) {
return (int) (Math.random() * max + min);
}
return bytes;
}
/**
* Generates a random number
*
* @param min
* Minimum value
* @param max
* Maximum value
* @return Random number
*/
protected int createRandomNumber(int min, int max) {
return (int) (Math.random() * max + min);
}
}

View File

@ -18,6 +18,7 @@ package org.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Map;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
@ -45,196 +46,206 @@ import org.jboss.netty.handler.codec.http.HttpVersion;
*/
public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
private byte[] expectedChallengeResponseBytes = null;
private byte[] expectedChallengeResponseBytes = null;
/**
* 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.
*/
public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subProtocol) {
super(webSocketURL, version, subProtocol);
}
/**
* 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 customHeaders
* Map of custom headers to add to the client request
*/
public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subProtocol,
Map<String, String> customHeaders) {
super(webSocketURL, version, subProtocol, customHeaders);
}
/**
* <p>
* Sends the opening request to the server:
* </p>
*
* <pre>
* 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
* </pre>
*
* @param channel
* Channel into which we can write our request
*/
@Override
public void performOpeningHandshake(Channel channel) {
// Make keys
int spaces1 = createRandomNumber(1, 12);
int spaces2 = createRandomNumber(1, 12);
/**
* <p>
* Sends the opening request to the server:
* </p>
*
* <pre>
* 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
* </pre>
*
* @param channel
* Channel into which we can write our request
*/
@Override
public void performOpeningHandshake(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 max1 = Integer.MAX_VALUE / spaces1;
int max2 = Integer.MAX_VALUE / spaces2;
int number1 = createRandomNumber(0, max1);
int number2 = createRandomNumber(0, max2);
int number1 = createRandomNumber(0, max1);
int number2 = createRandomNumber(0, max2);
int product1 = number1 * spaces1;
int product2 = number2 * spaces2;
int product1 = number1 * spaces1;
int product2 = number2 * spaces2;
String key1 = Integer.toString(product1);
String key2 = Integer.toString(product2);
String key1 = Integer.toString(product1);
String key2 = Integer.toString(product2);
key1 = insertRandomCharacters(key1);
key2 = insertRandomCharacters(key2);
key1 = insertRandomCharacters(key1);
key2 = insertRandomCharacters(key2);
key1 = insertSpaces(key1, spaces1);
key2 = insertSpaces(key2, spaces2);
key1 = insertSpaces(key1, spaces1);
key2 = insertSpaces(key2, spaces2);
byte[] key3 = createRandomBytes(8);
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();
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);
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();
}
// 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));
// 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());
}
if (customHeaders != null) {
for (String header : customHeaders.keySet()) {
request.addHeader(header, customHeaders.get(header));
}
}
request.setContent(ChannelBuffers.copiedBuffer(key3));
channel.write(request);
channel.write(request);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket00FrameEncoder());
}
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket00FrameEncoder());
}
/**
* <p>
* Process server response:
* </p>
*
* <pre>
* 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-
* </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, "WebSocket Protocol Handshake");
/**
* <p>
* Process server response:
* </p>
*
* <pre>
* 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-
* </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, "WebSocket Protocol Handshake");
if (!response.getStatus().equals(status)) {
throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
}
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 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));
}
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");
}
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);
String protocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
this.setSubProtocolResponse(protocol);
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket00FrameDecoder());
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket00FrameDecoder());
this.setOpenningHandshakeCompleted(true);
}
this.setOpenningHandshakeCompleted(true);
}
private String insertRandomCharacters(String key) {
int count = createRandomNumber(1, 12);
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;
}
}
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;
}
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;
}
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;
}
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;
}
return key;
}
}

View File

@ -16,6 +16,7 @@
package org.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
import java.util.Map;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
@ -42,8 +43,7 @@ import org.jboss.netty.util.CharsetUtil;
*/
public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
private static final InternalLogger logger = InternalLoggerFactory
.getInstance(WebSocketClientHandshaker08.class);
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker08.class);
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
@ -69,11 +69,12 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
* @param customHeaders
* Map of custom headers to add to the client request
*/
public WebSocketClientHandshaker08(URI webSocketURL,
WebSocketVersion version, String subProtocol,
boolean allowExtensions) {
super(webSocketURL, version, subProtocol);
public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subProtocol,
boolean allowExtensions, Map<String, String> customHeaders) {
super(webSocketURL, version, subProtocol, customHeaders);
this.allowExtensions = allowExtensions;
}
@ -115,14 +116,12 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
this.expectedChallengeResponseString = base64Encode(sha1);
if (logger.isDebugEnabled()) {
logger.debug(String
.format("WS Version 08 Client Handshake key: %s. Expected response: %s.",
key, this.expectedChallengeResponseString));
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);
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);
@ -132,11 +131,15 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
}
request.addHeader(Names.SEC_WEBSOCKET_VERSION, "8");
if (customHeaders != null) {
for (String header : customHeaders.keySet()) {
request.addHeader(header, customHeaders.get(header));
}
}
channel.write(request);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder",
new WebSocket08FrameEncoder(true));
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket08FrameEncoder(true));
}
/**
@ -160,36 +163,28 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
* @throws WebSocketHandshakeException
*/
@Override
public void performClosingHandshake(Channel channel, HttpResponse response)
throws WebSocketHandshakeException {
final HttpResponseStatus status = new HttpResponseStatus(101,
"Switching Protocols");
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());
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));
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));
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,
if (accept == null || !accept.equals(this.expectedChallengeResponseString)) {
throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept,
this.expectedChallengeResponseString));
}

View File

@ -16,6 +16,7 @@
package org.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
import java.util.Map;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
@ -42,8 +43,7 @@ import org.jboss.netty.util.CharsetUtil;
*/
public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
private static final InternalLogger logger = InternalLoggerFactory
.getInstance(WebSocketClientHandshaker13.class);
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker13.class);
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
@ -69,11 +69,12 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
* @param customHeaders
* Map of custom headers to add to the client request
*/
public WebSocketClientHandshaker13(URI webSocketURL,
WebSocketVersion version, String subProtocol,
boolean allowExtensions) {
super(webSocketURL, version, subProtocol);
public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subProtocol,
boolean allowExtensions, Map<String, String> customHeaders) {
super(webSocketURL, version, subProtocol, customHeaders);
this.allowExtensions = allowExtensions;
}
@ -115,14 +116,12 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
this.expectedChallengeResponseString = base64Encode(sha1);
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"WS Version 13 Client Handshake key: %s. Expected response: %s.",
key, this.expectedChallengeResponseString));
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);
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);
@ -132,11 +131,15 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
}
request.addHeader(Names.SEC_WEBSOCKET_VERSION, "13");
if (customHeaders != null) {
for (String header : customHeaders.keySet()) {
request.addHeader(header, customHeaders.get(header));
}
}
channel.write(request);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder",
new WebSocket13FrameEncoder(true));
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket13FrameEncoder(true));
}
/**
@ -160,36 +163,28 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
* @throws WebSocketHandshakeException
*/
@Override
public void performClosingHandshake(Channel channel, HttpResponse response)
throws WebSocketHandshakeException {
final HttpResponseStatus status = new HttpResponseStatus(101,
"Switching Protocols");
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());
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));
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));
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,
if (accept == null || !accept.equals(this.expectedChallengeResponseString)) {
throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept,
this.expectedChallengeResponseString));
}

View File

@ -16,42 +16,46 @@
package org.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
import java.util.Map;
/**
* Instances the appropriate handshake class to use for clients
*/
public class WebSocketClientHandshakerFactory {
/**
* Instances a new handshaker
*
* @param webSocketURL
* URL for web socket communications. e.g
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param version
* Version of web socket specification to use to connect to the
* server
* @param subProtocol
* Sub protocol request sent to the server. Null if no
* sub-protocol support is required.
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
* @throws WebSocketHandshakeException
*/
public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketVersion version, String subProtocol, boolean allowExtensions) throws WebSocketHandshakeException {
if (version == WebSocketVersion.V13) {
return new WebSocketClientHandshaker13(webSocketURL, version, subProtocol, allowExtensions);
}
if (version == WebSocketVersion.V08) {
return new WebSocketClientHandshaker08(webSocketURL, version, subProtocol, allowExtensions);
}
if (version == WebSocketVersion.V00) {
return new WebSocketClientHandshaker00(webSocketURL, version, subProtocol);
}
/**
* Instances a new handshaker
*
* @param webSocketURL
* URL for web socket communications. e.g
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param version
* Version of web socket specification to use to connect to the
* server
* @param subProtocol
* Sub protocol request sent to the server. Null if no
* sub-protocol support is required.
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
* @param customHeaders
* custom HTTP headers
* @throws WebSocketHandshakeException
*/
public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketVersion version, String subProtocol,
boolean allowExtensions, Map<String, String> customHeaders) throws WebSocketHandshakeException {
if (version == WebSocketVersion.V13) {
return new WebSocketClientHandshaker13(webSocketURL, version, subProtocol, allowExtensions, customHeaders);
}
if (version == WebSocketVersion.V08) {
return new WebSocketClientHandshaker08(webSocketURL, version, subProtocol, allowExtensions, customHeaders);
}
if (version == WebSocketVersion.V00) {
return new WebSocketClientHandshaker00(webSocketURL, version, subProtocol, customHeaders);
}
throw new WebSocketHandshakeException("Protocol version " + version.toString() + " not supported.");
throw new WebSocketHandshakeException("Protocol " + version.toString() + " not supported.");
}
}
}