Merge pull request #124 from veebs/WebSocketVersion3.2
Use web socket version rather than spec version
This commit is contained in:
commit
e9c0c40253
@ -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},
|
||||
|
@ -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;
|
||||
@ -28,7 +29,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
|
||||
@ -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"),
|
||||
WebSocketSpecificationVersion.V17, 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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -29,60 +29,65 @@ 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.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 WebSocketSpecificationVersion 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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -37,85 +38,91 @@ 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;
|
||||
|
||||
/**
|
||||
* 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 WebSocketSpecificationVersion 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, WebSocketSpecificationVersion 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;
|
||||
}
|
||||
}
|
@ -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 WebSocketSpecificationVersion version = WebSocketSpecificationVersion.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, WebSocketSpecificationVersion 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 WebSocketSpecificationVersion getVersion() {
|
||||
return version;
|
||||
}
|
||||
protected void setWebSocketURL(URI webSocketURL) {
|
||||
this.webSocketURL = webSocketURL;
|
||||
}
|
||||
|
||||
protected void setVersion(WebSocketSpecificationVersion 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);
|
||||
}
|
||||
}
|
@ -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, WebSocketSpecificationVersion 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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 java.util.Map;
|
||||
|
||||
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
|
||||
* @param customHeaders
|
||||
* Map of custom headers to add to the client request
|
||||
*/
|
||||
public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subProtocol,
|
||||
boolean allowExtensions, Map<String, String> customHeaders) {
|
||||
super(webSocketURL, version, subProtocol, customHeaders);
|
||||
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");
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* <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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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 java.util.Map;
|
||||
|
||||
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
|
||||
* @param customHeaders
|
||||
* Map of custom headers to add to the client request
|
||||
*/
|
||||
public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subProtocol,
|
||||
boolean allowExtensions, Map<String, String> customHeaders) {
|
||||
super(webSocketURL, version, subProtocol, customHeaders);
|
||||
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");
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* <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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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, WebSocketSpecificationVersion version, String subProtocol, boolean allowExtensions) throws WebSocketHandshakeException {
|
||||
if (version == WebSocketSpecificationVersion.V17) {
|
||||
return new WebSocketClientHandshaker17(webSocketURL, version, subProtocol, allowExtensions);
|
||||
}
|
||||
if (version == WebSocketSpecificationVersion.V10) {
|
||||
return new WebSocketClientHandshaker10(webSocketURL, version, subProtocol, allowExtensions);
|
||||
}
|
||||
if (version == WebSocketSpecificationVersion.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.");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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))) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
}
|
@ -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.");
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user