Merge pull request #127 from veebs/WebSocketVersion3.2

Format source
This commit is contained in:
Vibul Imtarnasan 2011-12-14 21:57:33 -08:00
commit 8145ad96f6
41 changed files with 1524 additions and 1546 deletions

View File

@ -25,8 +25,8 @@ import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
/** /**
* A Web Socket echo server for running the <a href="http://www.tavendo.de/autobahn/testsuite.html">autobahn</a> * A Web Socket echo server for running the <a href="http://www.tavendo.de/autobahn/testsuite.html">autobahn</a> test
* test suite * suite
*/ */
public class WebSocketServer { public class WebSocketServer {
public static void main(String[] args) { public static void main(String[] args) {
@ -34,19 +34,19 @@ public class WebSocketServer {
ch.setLevel(Level.FINE); ch.setLevel(Level.FINE);
Logger.getLogger("").addHandler(ch); Logger.getLogger("").addHandler(ch);
Logger.getLogger("").setLevel(Level.FINE); Logger.getLogger("").setLevel(Level.FINE);
// Configure the server. // Configure the server.
ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory( ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool())); Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
//bootstrap.setOption("child.tcpNoDelay", true); // bootstrap.setOption("child.tcpNoDelay", true);
// Set up the event pipeline factory. // Set up the event pipeline factory.
bootstrap.setPipelineFactory(new WebSocketServerPipelineFactory()); bootstrap.setPipelineFactory(new WebSocketServerPipelineFactory());
// Bind and start to accept incoming connections. // Bind and start to accept incoming connections.
bootstrap.bind(new InetSocketAddress(9000)); bootstrap.bind(new InetSocketAddress(9000));
System.out.println("Web Socket Server started on localhost:9000."); System.out.println("Web Socket Server started on localhost:9000.");
} }
} }

View File

@ -90,8 +90,9 @@ public class WebSocketServerHandler extends SimpleChannelUpstreamHandler {
ctx.getChannel().write( ctx.getChannel().write(
new PongWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); new PongWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
} else if (frame instanceof TextWebSocketFrame) { } else if (frame instanceof TextWebSocketFrame) {
//String text = ((TextWebSocketFrame) frame).getText(); // String text = ((TextWebSocketFrame) frame).getText();
ctx.getChannel().write(new TextWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); ctx.getChannel().write(
new TextWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
} else if (frame instanceof BinaryWebSocketFrame) { } else if (frame instanceof BinaryWebSocketFrame) {
ctx.getChannel().write( ctx.getChannel().write(
new BinaryWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); new BinaryWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));

View File

@ -24,7 +24,6 @@ package org.jboss.netty.example.http.websocketx.client;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame; import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
/** /**
* Copied from https://github.com/cgbystrom/netty-tools * Copied from https://github.com/cgbystrom/netty-tools
* *
@ -32,37 +31,37 @@ import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
*/ */
public interface WebSocketCallback { public interface WebSocketCallback {
/** /**
* Called when the client is connected to the server * Called when the client is connected to the server
* *
* @param client * @param client
* Current client used to connect * Current client used to connect
*/ */
void onConnect(WebSocketClient client); void onConnect(WebSocketClient client);
/** /**
* Called when the client got disconnected from the server. * Called when the client got disconnected from the server.
* *
* @param client * @param client
* Current client that was disconnected * Current client that was disconnected
*/ */
void onDisconnect(WebSocketClient client); void onDisconnect(WebSocketClient client);
/** /**
* Called when a message arrives from the server. * Called when a message arrives from the server.
* *
* @param client * @param client
* Current client connected * Current client connected
* @param frame * @param frame
* Data received from server * Data received from server
*/ */
void onMessage(WebSocketClient client, WebSocketFrame frame); void onMessage(WebSocketClient client, WebSocketFrame frame);
/** /**
* Called when an unhandled errors occurs. * Called when an unhandled errors occurs.
* *
* @param t * @param t
* The causing error * The causing error
*/ */
void onError(Throwable t); void onError(Throwable t);
} }

View File

@ -38,8 +38,8 @@ import java.util.concurrent.Executors;
/** /**
* Copied from https://github.com/cgbystrom/netty-tools * Copied from https://github.com/cgbystrom/netty-tools
* *
* A factory for creating WebSocket clients. The entry point for creating and * A factory for creating WebSocket clients. The entry point for creating and connecting a client. Can and should be
* connecting a client. Can and should be used to create multiple instances. * used to create multiple instances.
*/ */
public class WebSocketClientFactory { public class WebSocketClientFactory {
@ -57,8 +57,7 @@ public class WebSocketClientFactory {
* Callback interface to receive events * Callback interface to receive events
* @param customHeaders * @param customHeaders
* Map of custom headers to add to the client request * Map of custom headers to add to the client request
* @return A WebSocket client. Call {@link WebSocketClient#connect()} to * @return A WebSocket client. Call {@link WebSocketClient#connect()} to connect.
* connect.
*/ */
public WebSocketClient newClient(final URI url, final WebSocketVersion version, final WebSocketCallback callback, public WebSocketClient newClient(final URI url, final WebSocketVersion version, final WebSocketCallback callback,
final Map<String, String> customHeaders) { final Map<String, String> customHeaders) {

View File

@ -44,9 +44,8 @@ import org.jboss.netty.util.CharsetUtil;
/** /**
* Copied from https://github.com/cgbystrom/netty-tools * Copied from https://github.com/cgbystrom/netty-tools
* *
* Handles socket communication for a connected WebSocket client Not intended * Handles socket communication for a connected WebSocket client Not intended for end-users. Please use
* for end-users. Please use {@link WebSocketClient} or * {@link WebSocketClient} or {@link WebSocketCallback} for controlling your client.
* {@link WebSocketCallback} for controlling your client.
*/ */
public class WebSocketClientHandler extends SimpleChannelUpstreamHandler implements WebSocketClient { public class WebSocketClientHandler extends SimpleChannelUpstreamHandler implements WebSocketClient {

View File

@ -30,15 +30,15 @@ import java.io.IOException;
*/ */
public class WebSocketException extends IOException { public class WebSocketException extends IOException {
/** /**
*/ */
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public WebSocketException(String s) { public WebSocketException(String s) {
super(s); super(s);
} }
public WebSocketException(String s, Throwable throwable) { public WebSocketException(String s, Throwable throwable) {
super(s, throwable); super(s, throwable);
} }
} }

View File

@ -25,27 +25,27 @@ import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
*/ */
public class WebSocketHttpResponseDecoder extends HttpResponseDecoder { public class WebSocketHttpResponseDecoder extends HttpResponseDecoder {
@Override @Override
protected boolean isContentAlwaysEmpty(HttpMessage msg) { protected boolean isContentAlwaysEmpty(HttpMessage msg) {
if (msg instanceof HttpResponse) { if (msg instanceof HttpResponse) {
HttpResponse res = (HttpResponse) msg; HttpResponse res = (HttpResponse) msg;
int code = res.getStatus().getCode(); int code = res.getStatus().getCode();
// FIX force reading of protocol upgrade challenge data into the content buffer // FIX force reading of protocol upgrade challenge data into the content buffer
if (code == 101) { if (code == 101) {
return false; return false;
} }
if (code < 200) { if (code < 200) {
return true; return true;
} }
switch (code) { switch (code) {
case 204: case 204:
case 205: case 205:
case 304: case 304:
return true; return true;
} }
} }
return false; return false;
} }
} }

View File

@ -29,11 +29,10 @@ import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
* *
* http://localhost:8080/websocket * http://localhost:8080/websocket
* *
* Open your browser at http://localhost:8080/, then the demo page will be * Open your browser at http://localhost:8080/, then the demo page will be loaded and a Web Socket connection will be
* loaded and a Web Socket connection will be made automatically. * made automatically.
* *
* This server illustrates support for the different web socket specification * This server illustrates support for the different web socket specification versions and will work with:
* versions and will work with:
* *
* <ul> * <ul>
* <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00) * <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00)
@ -52,7 +51,7 @@ public class WebSocketServer {
ch.setLevel(Level.FINE); ch.setLevel(Level.FINE);
Logger.getLogger("").addHandler(ch); Logger.getLogger("").addHandler(ch);
Logger.getLogger("").setLevel(Level.FINE); Logger.getLogger("").setLevel(Level.FINE);
// Configure the server. // Configure the server.
ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory( ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool())); Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
@ -62,7 +61,8 @@ public class WebSocketServer {
// Bind and start to accept incoming connections. // Bind and start to accept incoming connections.
bootstrap.bind(new InetSocketAddress(8080)); bootstrap.bind(new InetSocketAddress(8080));
System.out.println("Web Socket Server started on 8080. Open your browser and navigate to http://localhost:8080/"); System.out
.println("Web Socket Server started on 8080. Open your browser and navigate to http://localhost:8080/");
} }
} }

View File

@ -19,49 +19,74 @@ import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.util.CharsetUtil; import org.jboss.netty.util.CharsetUtil;
/** /**
* Generates the demo HTML page which is served at http://localhost:8080/ * Generates the demo HTML page which is served at http://localhost:8080/
*/ */
public class WebSocketServerIndexPage { public class WebSocketServerIndexPage {
private static final String NEWLINE = "\r\n"; private static final String NEWLINE = "\r\n";
public static ChannelBuffer getContent(String webSocketLocation) { public static ChannelBuffer getContent(String webSocketLocation) {
return ChannelBuffers.copiedBuffer( return ChannelBuffers
"<html><head><title>Web Socket Test</title></head>" + NEWLINE + .copiedBuffer(
"<body>" + NEWLINE + "<html><head><title>Web Socket Test</title></head>"
"<script type=\"text/javascript\">" + NEWLINE + + NEWLINE
"var socket;" + NEWLINE + + "<body>"
"if (!window.WebSocket) {" + NEWLINE + + NEWLINE
" window.WebSocket = window.MozWebSocket;" + NEWLINE + + "<script type=\"text/javascript\">"
"}" + NEWLINE + + NEWLINE
"if (window.WebSocket) {" + NEWLINE + + "var socket;"
" socket = new WebSocket(\"" + webSocketLocation + "\");" + NEWLINE + + NEWLINE
" socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };" + NEWLINE + + "if (!window.WebSocket) {"
" socket.onopen = function(event) { var ta = document.getElementById('responseText'); ta.value = \"Web Socket opened!\"; };" + NEWLINE + + NEWLINE
" socket.onclose = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + \"Web Socket closed\"; };" + NEWLINE + + " window.WebSocket = window.MozWebSocket;"
"} else {" + NEWLINE + + NEWLINE
" alert(\"Your browser does not support Web Socket.\");" + NEWLINE + + "}"
"}" + NEWLINE + + NEWLINE
NEWLINE + + "if (window.WebSocket) {"
"function send(message) {" + NEWLINE + + NEWLINE
" if (!window.WebSocket) { return; }" + NEWLINE + + " socket = new WebSocket(\""
" if (socket.readyState == WebSocket.OPEN) {" + NEWLINE + + webSocketLocation
" socket.send(message);" + NEWLINE + + "\");"
" } else {" + NEWLINE + + NEWLINE
" alert(\"The socket is not open.\");" + NEWLINE + + " socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };"
" }" + NEWLINE + + NEWLINE
"}" + NEWLINE + + " socket.onopen = function(event) { var ta = document.getElementById('responseText'); ta.value = \"Web Socket opened!\"; };"
"</script>" + NEWLINE + + NEWLINE
"<form onsubmit=\"return false;\">" + NEWLINE + + " socket.onclose = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + \"Web Socket closed\"; };"
"<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>" + + NEWLINE
"<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />" + NEWLINE + + "} else {"
"<h3>Output</h3>" + NEWLINE + + NEWLINE
"<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>" + NEWLINE + + " alert(\"Your browser does not support Web Socket.\");"
"</form>" + NEWLINE + + NEWLINE
"</body>" + NEWLINE + + "}"
"</html>" + NEWLINE, + NEWLINE
CharsetUtil.US_ASCII); + NEWLINE
} + "function send(message) {"
+ NEWLINE
+ " if (!window.WebSocket) { return; }"
+ NEWLINE
+ " if (socket.readyState == WebSocket.OPEN) {"
+ NEWLINE
+ " socket.send(message);"
+ NEWLINE
+ " } else {"
+ NEWLINE
+ " alert(\"The socket is not open.\");"
+ NEWLINE
+ " }"
+ NEWLINE
+ "}"
+ NEWLINE
+ "</script>"
+ NEWLINE
+ "<form onsubmit=\"return false;\">"
+ NEWLINE
+ "<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>"
+ "<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />"
+ NEWLINE + "<h3>Output</h3>" + NEWLINE
+ "<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>"
+ NEWLINE + "</form>" + NEWLINE + "</body>" + NEWLINE + "</html>" + NEWLINE,
CharsetUtil.US_ASCII);
}
} }

View File

@ -29,11 +29,10 @@ import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
* *
* https://localhost:8081/websocket * https://localhost:8081/websocket
* *
* Open your browser at https://localhost:8081/, then the demo page will be * Open your browser at https://localhost:8081/, then the demo page will be loaded and a Web Socket connection will be
* loaded and a Web Socket connection will be made automatically. * made automatically.
* *
* This server illustrates support for the different web socket specification * This server illustrates support for the different web socket specification versions and will work with:
* versions and will work with:
* *
* <ul> * <ul>
* <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00) * <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00)
@ -52,13 +51,13 @@ public class WebSocketSslServer {
ch.setLevel(Level.FINE); ch.setLevel(Level.FINE);
Logger.getLogger("").addHandler(ch); Logger.getLogger("").addHandler(ch);
Logger.getLogger("").setLevel(Level.FINE); Logger.getLogger("").setLevel(Level.FINE);
String keyStoreFilePath = System.getProperty("keystore.file.path"); String keyStoreFilePath = System.getProperty("keystore.file.path");
if (keyStoreFilePath == null || keyStoreFilePath.isEmpty()) { if (keyStoreFilePath == null || keyStoreFilePath.isEmpty()) {
System.out.println("ERROR: System property keystore.file.path not set. Exiting now!"); System.out.println("ERROR: System property keystore.file.path not set. Exiting now!");
System.exit(1); System.exit(1);
} }
String keyStoreFilePassword = System.getProperty("keystore.file.password"); String keyStoreFilePassword = System.getProperty("keystore.file.password");
if (keyStoreFilePassword == null || keyStoreFilePassword.isEmpty()) { if (keyStoreFilePassword == null || keyStoreFilePassword.isEmpty()) {
System.out.println("ERROR: System property keystore.file.password not set. Exiting now!"); System.out.println("ERROR: System property keystore.file.password not set. Exiting now!");
@ -74,7 +73,8 @@ public class WebSocketSslServer {
// Bind and start to accept incoming connections. // Bind and start to accept incoming connections.
bootstrap.bind(new InetSocketAddress(8081)); bootstrap.bind(new InetSocketAddress(8081));
System.out.println("Web Socket Server started on 8081. Open your browser and navigate to https://localhost:8081/"); System.out
.println("Web Socket Server started on 8081. Open your browser and navigate to https://localhost:8081/");
} }
} }

View File

@ -19,49 +19,74 @@ import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.util.CharsetUtil; import org.jboss.netty.util.CharsetUtil;
/** /**
* Generates the demo HTML page which is served at http://localhost:8080/ * Generates the demo HTML page which is served at http://localhost:8080/
*/ */
public class WebSocketSslServerIndexPage { public class WebSocketSslServerIndexPage {
private static final String NEWLINE = "\r\n"; private static final String NEWLINE = "\r\n";
public static ChannelBuffer getContent(String webSocketLocation) { public static ChannelBuffer getContent(String webSocketLocation) {
return ChannelBuffers.copiedBuffer( return ChannelBuffers
"<html><head><title>Web Socket Test</title></head>" + NEWLINE + .copiedBuffer(
"<body>" + NEWLINE + "<html><head><title>Web Socket Test</title></head>"
"<script type=\"text/javascript\">" + NEWLINE + + NEWLINE
"var socket;" + NEWLINE + + "<body>"
"if (!window.WebSocket) {" + NEWLINE + + NEWLINE
" window.WebSocket = window.MozWebSocket;" + NEWLINE + + "<script type=\"text/javascript\">"
"}" + NEWLINE + + NEWLINE
"if (window.WebSocket) {" + NEWLINE + + "var socket;"
" socket = new WebSocket(\"" + webSocketLocation + "\");" + NEWLINE + + NEWLINE
" socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };" + NEWLINE + + "if (!window.WebSocket) {"
" socket.onopen = function(event) { var ta = document.getElementById('responseText'); ta.value = \"Web Socket opened!\"; };" + NEWLINE + + NEWLINE
" socket.onclose = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + \"Web Socket closed\"; };" + NEWLINE + + " window.WebSocket = window.MozWebSocket;"
"} else {" + NEWLINE + + NEWLINE
" alert(\"Your browser does not support Web Socket.\");" + NEWLINE + + "}"
"}" + NEWLINE + + NEWLINE
NEWLINE + + "if (window.WebSocket) {"
"function send(message) {" + NEWLINE + + NEWLINE
" if (!window.WebSocket) { return; }" + NEWLINE + + " socket = new WebSocket(\""
" if (socket.readyState == WebSocket.OPEN) {" + NEWLINE + + webSocketLocation
" socket.send(message);" + NEWLINE + + "\");"
" } else {" + NEWLINE + + NEWLINE
" alert(\"The socket is not open.\");" + NEWLINE + + " socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };"
" }" + NEWLINE + + NEWLINE
"}" + NEWLINE + + " socket.onopen = function(event) { var ta = document.getElementById('responseText'); ta.value = \"Web Socket opened!\"; };"
"</script>" + NEWLINE + + NEWLINE
"<form onsubmit=\"return false;\">" + NEWLINE + + " socket.onclose = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + \"Web Socket closed\"; };"
"<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>" + + NEWLINE
"<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />" + NEWLINE + + "} else {"
"<h3>Output</h3>" + NEWLINE + + NEWLINE
"<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>" + NEWLINE + + " alert(\"Your browser does not support Web Socket.\");"
"</form>" + NEWLINE + + NEWLINE
"</body>" + NEWLINE + + "}"
"</html>" + NEWLINE, + NEWLINE
CharsetUtil.US_ASCII); + NEWLINE
} + "function send(message) {"
+ NEWLINE
+ " if (!window.WebSocket) { return; }"
+ NEWLINE
+ " if (socket.readyState == WebSocket.OPEN) {"
+ NEWLINE
+ " socket.send(message);"
+ NEWLINE
+ " } else {"
+ NEWLINE
+ " alert(\"The socket is not open.\");"
+ NEWLINE
+ " }"
+ NEWLINE
+ "}"
+ NEWLINE
+ "</script>"
+ NEWLINE
+ "<form onsubmit=\"return false;\">"
+ NEWLINE
+ "<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>"
+ "<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />"
+ NEWLINE + "<h3>Output</h3>" + NEWLINE
+ "<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>"
+ NEWLINE + "</form>" + NEWLINE + "</body>" + NEWLINE + "</html>" + NEWLINE,
CharsetUtil.US_ASCII);
}
} }

View File

@ -32,10 +32,10 @@ public class WebSocketSslServerPipelineFactory implements ChannelPipelineFactory
public ChannelPipeline getPipeline() throws Exception { public ChannelPipeline getPipeline() throws Exception {
// Create a default pipeline implementation. // Create a default pipeline implementation.
ChannelPipeline pipeline = pipeline(); ChannelPipeline pipeline = pipeline();
SSLEngine engine = WebSocketSslServerSslContext.getInstance().getServerContext().createSSLEngine(); SSLEngine engine = WebSocketSslServerSslContext.getInstance().getServerContext().createSSLEngine();
engine.setUseClientMode(false); engine.setUseClientMode(false);
pipeline.addLast("ssl", new SslHandler(engine)); pipeline.addLast("ssl", new SslHandler(engine));
pipeline.addLast("decoder", new HttpRequestDecoder()); pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpChunkAggregator(65536)); pipeline.addLast("aggregator", new HttpChunkAggregator(65536));

View File

@ -42,9 +42,8 @@ public class WebSocketSslServerSslContext {
} }
/** /**
* SingletonHolder is loaded on the first execution of * SingletonHolder is loaded on the first execution of Singleton.getInstance() or the first access to
* Singleton.getInstance() or the first access to SingletonHolder.INSTANCE, * SingletonHolder.INSTANCE, not before.
* not before.
* *
* See http://en.wikipedia.org/wiki/Singleton_pattern * See http://en.wikipedia.org/wiki/Singleton_pattern
*/ */

View File

@ -23,44 +23,42 @@ import org.jboss.netty.buffer.ChannelBuffers;
*/ */
public class BinaryWebSocketFrame extends WebSocketFrame { public class BinaryWebSocketFrame extends WebSocketFrame {
/** /**
* Creates a new empty binary frame. * Creates a new empty binary frame.
*/ */
public BinaryWebSocketFrame() { public BinaryWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} }
/** /**
* Creates a new binary frame with the specified binary data. The final * Creates a new binary frame with the specified binary data. The final fragment flag is set to true.
* fragment flag is set to true. *
* * @param binaryData
* @param binaryData * the content of the frame.
* the content of the frame. */
*/ public BinaryWebSocketFrame(ChannelBuffer binaryData) {
public BinaryWebSocketFrame(ChannelBuffer binaryData) { this.setBinaryData(binaryData);
this.setBinaryData(binaryData); }
}
/** /**
* Creates a new binary frame with the specified binary data and the final * Creates a new binary frame with the specified binary data and the final fragment flag.
* fragment flag. *
* * @param finalFragment
* @param finalFragment * flag indicating if this frame is the final fragment
* flag indicating if this frame is the final fragment * @param rsv
* @param rsv * reserved bits used for protocol extensions
* reserved bits used for protocol extensions * @param binaryData
* @param binaryData * the content of the frame.
* the content of the frame. */
*/ public BinaryWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
public BinaryWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) { this.setFinalFragment(finalFragment);
this.setFinalFragment(finalFragment); this.setRsv(rsv);
this.setRsv(rsv); this.setBinaryData(binaryData);
this.setBinaryData(binaryData); }
}
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')'; return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
} }
} }

View File

@ -22,28 +22,28 @@ import org.jboss.netty.buffer.ChannelBuffers;
*/ */
public class CloseWebSocketFrame extends WebSocketFrame { public class CloseWebSocketFrame extends WebSocketFrame {
/** /**
* Creates a new empty close frame. * Creates a new empty close frame.
*/ */
public CloseWebSocketFrame() { public CloseWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} }
/** /**
* Creates a new close frame * Creates a new close frame
* *
* @param finalFragment * @param finalFragment
* flag indicating if this frame is the final fragment * flag indicating if this frame is the final fragment
* @param rsv * @param rsv
* reserved bits used for protocol extensions * reserved bits used for protocol extensions
*/ */
public CloseWebSocketFrame(boolean finalFragment, int rsv) { public CloseWebSocketFrame(boolean finalFragment, int rsv) {
this.setFinalFragment(finalFragment); this.setFinalFragment(finalFragment);
this.setRsv(rsv); this.setRsv(rsv);
} }
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName(); return getClass().getSimpleName();
} }
} }

View File

@ -20,123 +20,119 @@ import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.util.CharsetUtil; import org.jboss.netty.util.CharsetUtil;
/** /**
* Web Socket continuation frame containing continuation text or binary data. * Web Socket continuation frame containing continuation text or binary data. This is used for fragmented messages where
* This is used for fragmented messages where the contents of a messages is * the contents of a messages is contained more than 1 frame.
* contained more than 1 frame.
*/ */
public class ContinuationWebSocketFrame extends WebSocketFrame { public class ContinuationWebSocketFrame extends WebSocketFrame {
private String aggregatedText = null; private String aggregatedText = null;
/** /**
* Creates a new empty continuation frame. * Creates a new empty continuation frame.
*/ */
public ContinuationWebSocketFrame() { public ContinuationWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} }
/** /**
* Creates a new continuation frame with the specified binary data. The * Creates a new continuation frame with the specified binary data. The final fragment flag is set to true.
* final fragment flag is set to true. *
* * @param binaryData
* @param binaryData * the content of the frame.
* the content of the frame. */
*/ public ContinuationWebSocketFrame(ChannelBuffer binaryData) {
public ContinuationWebSocketFrame(ChannelBuffer binaryData) { this.setBinaryData(binaryData);
this.setBinaryData(binaryData); }
}
/** /**
* Creates a new continuation frame with the specified binary data * Creates a new continuation frame with the specified binary data
* *
* @param finalFragment * @param finalFragment
* flag indicating if this frame is the final fragment * flag indicating if this frame is the final fragment
* @param rsv * @param rsv
* reserved bits used for protocol extensions * reserved bits used for protocol extensions
* @param binaryData * @param binaryData
* the content of the frame. * the content of the frame.
*/ */
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) { public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment); this.setFinalFragment(finalFragment);
this.setRsv(rsv); this.setRsv(rsv);
this.setBinaryData(binaryData); this.setBinaryData(binaryData);
} }
/** /**
* Creates a new continuation frame with the specified binary data * Creates a new continuation frame with the specified binary data
* *
* @param finalFragment * @param finalFragment
* flag indicating if this frame is the final fragment * flag indicating if this frame is the final fragment
* @param rsv * @param rsv
* reserved bits used for protocol extensions * reserved bits used for protocol extensions
* @param binaryData * @param binaryData
* the content of the frame. * the content of the frame.
* @param aggregatedText * @param aggregatedText
* Aggregated text set by decoder on the final continuation frame * Aggregated text set by decoder on the final continuation frame of a fragmented text message
* of a fragmented text message */
*/ public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData, String aggregatedText) {
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData, String aggregatedText) { this.setFinalFragment(finalFragment);
this.setFinalFragment(finalFragment); this.setRsv(rsv);
this.setRsv(rsv); this.setBinaryData(binaryData);
this.setBinaryData(binaryData); this.aggregatedText = aggregatedText;
this.aggregatedText = aggregatedText; }
}
/** /**
* Creates a new continuation frame with the specified text data * Creates a new continuation frame with the specified text data
* *
* @param finalFragment * @param finalFragment
* flag indicating if this frame is the final fragment * flag indicating if this frame is the final fragment
* @param rsv * @param rsv
* reserved bits used for protocol extensions * reserved bits used for protocol extensions
* @param text * @param text
* text content of the frame. * text content of the frame.
*/ */
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, String text) { public ContinuationWebSocketFrame(boolean finalFragment, int rsv, String text) {
this.setFinalFragment(finalFragment); this.setFinalFragment(finalFragment);
this.setRsv(rsv); this.setRsv(rsv);
this.setText(text); this.setText(text);
} }
/** /**
* Returns the text data in this frame * Returns the text data in this frame
*/ */
public String getText() { public String getText() {
if (this.getBinaryData() == null) { if (this.getBinaryData() == null) {
return null; return null;
} }
return this.getBinaryData().toString(CharsetUtil.UTF_8); return this.getBinaryData().toString(CharsetUtil.UTF_8);
} }
/** /**
* Sets the string for this frame * Sets the string for this frame
* *
* @param text * @param text
* text to store * text to store
*/ */
public void setText(String text) { public void setText(String text) {
if (text == null || text.isEmpty()) { if (text == null || text.isEmpty()) {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else { } else {
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8)); this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
} }
} }
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')'; return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
} }
/** /**
* Aggregated text returned by decoder on the final continuation frame of a * Aggregated text returned by decoder on the final continuation frame of a fragmented text message
* fragmented text message */
*/ public String getAggregatedText() {
public String getAggregatedText() { return aggregatedText;
return aggregatedText; }
}
public void setAggregatedText(String aggregatedText) { public void setAggregatedText(String aggregatedText) {
this.aggregatedText = aggregatedText; this.aggregatedText = aggregatedText;
} }
} }

View File

@ -23,43 +23,43 @@ import org.jboss.netty.buffer.ChannelBuffers;
*/ */
public class PingWebSocketFrame extends WebSocketFrame { public class PingWebSocketFrame extends WebSocketFrame {
/** /**
* Creates a new empty ping frame. * Creates a new empty ping frame.
*/ */
public PingWebSocketFrame() { public PingWebSocketFrame() {
this.setFinalFragment(true); this.setFinalFragment(true);
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} }
/** /**
* Creates a new ping frame with the specified binary data. * Creates a new ping frame with the specified binary data.
* *
* @param binaryData * @param binaryData
* the content of the frame. * the content of the frame.
*/ */
public PingWebSocketFrame(ChannelBuffer binaryData) { public PingWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData); this.setBinaryData(binaryData);
} }
/** /**
* Creates a new ping frame with the specified binary data * Creates a new ping frame with the specified binary data
* *
* @param finalFragment * @param finalFragment
* flag indicating if this frame is the final fragment * flag indicating if this frame is the final fragment
* @param rsv * @param rsv
* reserved bits used for protocol extensions * reserved bits used for protocol extensions
* @param binaryData * @param binaryData
* the content of the frame. * the content of the frame.
*/ */
public PingWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) { public PingWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment); this.setFinalFragment(finalFragment);
this.setRsv(rsv); this.setRsv(rsv);
this.setBinaryData(binaryData); this.setBinaryData(binaryData);
} }
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')'; return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
} }
} }

View File

@ -23,42 +23,42 @@ import org.jboss.netty.buffer.ChannelBuffers;
*/ */
public class PongWebSocketFrame extends WebSocketFrame { public class PongWebSocketFrame extends WebSocketFrame {
/** /**
* Creates a new empty pong frame. * Creates a new empty pong frame.
*/ */
public PongWebSocketFrame() { public PongWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} }
/** /**
* Creates a new pong frame with the specified binary data. * Creates a new pong frame with the specified binary data.
* *
* @param binaryData * @param binaryData
* the content of the frame. * the content of the frame.
*/ */
public PongWebSocketFrame(ChannelBuffer binaryData) { public PongWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData); this.setBinaryData(binaryData);
} }
/** /**
* Creates a new pong frame with the specified binary data * Creates a new pong frame with the specified binary data
* *
* @param finalFragment * @param finalFragment
* flag indicating if this frame is the final fragment * flag indicating if this frame is the final fragment
* @param rsv * @param rsv
* reserved bits used for protocol extensions * reserved bits used for protocol extensions
* @param binaryData * @param binaryData
* the content of the frame. * the content of the frame.
*/ */
public PongWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) { public PongWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment); this.setFinalFragment(finalFragment);
this.setRsv(rsv); this.setRsv(rsv);
this.setBinaryData(binaryData); this.setBinaryData(binaryData);
} }
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')'; return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
} }
} }

View File

@ -24,102 +24,98 @@ import org.jboss.netty.util.CharsetUtil;
*/ */
public class TextWebSocketFrame extends WebSocketFrame { public class TextWebSocketFrame extends WebSocketFrame {
/** /**
* Creates a new empty text frame. * Creates a new empty text frame.
*/ */
public TextWebSocketFrame() { public TextWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} }
/** /**
* Creates a new text frame with the specified text string. The final * Creates a new text frame with the specified text string. The final fragment flag is set to true.
* fragment flag is set to true. *
* * @param text
* @param text * String to put in the frame
* String to put in the frame */
*/ public TextWebSocketFrame(String text) {
public TextWebSocketFrame(String text) { if (text == null || text.isEmpty()) {
if (text == null || text.isEmpty()) { this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); } else {
} else { this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8)); }
} }
}
/** /**
* Creates a new text frame with the specified binary data. The final * Creates a new text frame with the specified binary data. The final fragment flag is set to true.
* fragment flag is set to true. *
* * @param binaryData
* @param binaryData * the content of the frame. Must be UTF-8 encoded
* the content of the frame. Must be UTF-8 encoded */
*/ public TextWebSocketFrame(ChannelBuffer binaryData) {
public TextWebSocketFrame(ChannelBuffer binaryData) { this.setBinaryData(binaryData);
this.setBinaryData(binaryData); }
}
/** /**
* Creates a new text frame with the specified text string. The final * Creates a new text frame with the specified text string. The final fragment flag is set to true.
* fragment flag is set to true. *
* * @param finalFragment
* @param finalFragment * flag indicating if this frame is the final fragment
* flag indicating if this frame is the final fragment * @param rsv
* @param rsv * reserved bits used for protocol extensions
* reserved bits used for protocol extensions * @param text
* @param text * String to put in the frame
* String to put in the frame */
*/ public TextWebSocketFrame(boolean finalFragment, int rsv, String text) {
public TextWebSocketFrame(boolean finalFragment, int rsv, String text) { this.setFinalFragment(finalFragment);
this.setFinalFragment(finalFragment); this.setRsv(rsv);
this.setRsv(rsv); if (text == null || text.isEmpty()) {
if (text == null || text.isEmpty()) { this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); } else {
} else { this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8)); }
} }
}
/** /**
* Creates a new text frame with the specified binary data. The final * Creates a new text frame with the specified binary data. The final fragment flag is set to true.
* fragment flag is set to true. *
* * @param finalFragment
* @param finalFragment * flag indicating if this frame is the final fragment
* flag indicating if this frame is the final fragment * @param rsv
* @param rsv * reserved bits used for protocol extensions
* reserved bits used for protocol extensions * @param binaryData
* @param binaryData * the content of the frame. Must be UTF-8 encoded
* the content of the frame. Must be UTF-8 encoded */
*/ public TextWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
public TextWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) { this.setFinalFragment(finalFragment);
this.setFinalFragment(finalFragment); this.setRsv(rsv);
this.setRsv(rsv); this.setBinaryData(binaryData);
this.setBinaryData(binaryData); }
}
/** /**
* Returns the text data in this frame * Returns the text data in this frame
*/ */
public String getText() { public String getText() {
if (this.getBinaryData() == null) { if (this.getBinaryData() == null) {
return null; return null;
} }
return this.getBinaryData().toString(CharsetUtil.UTF_8); return this.getBinaryData().toString(CharsetUtil.UTF_8);
} }
/** /**
* Sets the string for this frame * Sets the string for this frame
* *
* @param text * @param text
* text to store * text to store
*/ */
public void setText(String text) { public void setText(String text) {
if (text == null) { if (text == null) {
throw new NullPointerException("text"); throw new NullPointerException("text");
} }
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8)); this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
} }
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + "(text: " + getText() + ')'; return getClass().getSimpleName() + "(text: " + getText() + ')';
} }
} }

View File

@ -23,9 +23,9 @@ package org.jboss.netty.handler.codec.http.websocketx;
* Invalid UTF8 bytes encountered * Invalid UTF8 bytes encountered
*/ */
final class UTF8Exception extends RuntimeException { final class UTF8Exception extends RuntimeException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
UTF8Exception(String reason) { UTF8Exception(String reason) {
super(reason); super(reason);
} }
} }

View File

@ -23,51 +23,59 @@ package org.jboss.netty.handler.codec.http.websocketx;
* Checks UTF8 bytes for validity before converting it into a string * Checks UTF8 bytes for validity before converting it into a string
*/ */
final class UTF8Output { final class UTF8Output {
private static final int UTF8_ACCEPT = 0; private static final int UTF8_ACCEPT = 0;
private static final int UTF8_REJECT = 12; private static final int UTF8_REJECT = 12;
private static final byte[] TYPES = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, private static final byte[] TYPES = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }; 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11,
6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 };
private static final byte[] STATES = { 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, private static final byte[] STATES = { 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12,
12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 }; 12, 12, 12, 12, 12, 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24,
12, 12, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12,
12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12,
12, 12, 12, 12, 12, 12, 12, 12 };
private int state = UTF8_ACCEPT; private int state = UTF8_ACCEPT;
private int codep = 0; private int codep = 0;
private final StringBuilder stringBuilder; private final StringBuilder stringBuilder;
UTF8Output(byte[] bytes) { UTF8Output(byte[] bytes) {
stringBuilder = new StringBuilder(bytes.length); stringBuilder = new StringBuilder(bytes.length);
write(bytes); write(bytes);
} }
public void write(byte[] bytes) { public void write(byte[] bytes) {
for (byte b : bytes) { for (byte b : bytes) {
write(b); write(b);
} }
} }
public void write(int b) { public void write(int b) {
byte type = TYPES[b & 0xFF]; byte type = TYPES[b & 0xFF];
codep = (state != UTF8_ACCEPT) ? (b & 0x3f) | (codep << 6) : (0xff >> type) & (b); codep = (state != UTF8_ACCEPT) ? (b & 0x3f) | (codep << 6) : (0xff >> type) & (b);
state = STATES[state + type]; state = STATES[state + type];
if (state == UTF8_ACCEPT) { if (state == UTF8_ACCEPT) {
stringBuilder.append((char) codep); stringBuilder.append((char) codep);
} else if (state == UTF8_REJECT) { } else if (state == UTF8_REJECT) {
throw new UTF8Exception("bytes are not UTF-8"); throw new UTF8Exception("bytes are not UTF-8");
} }
} }
@Override @Override
public String toString() { public String toString() {
if (state != UTF8_ACCEPT) { if (state != UTF8_ACCEPT) {
throw new UTF8Exception("bytes are not UTF-8"); throw new UTF8Exception("bytes are not UTF-8");
} }
return stringBuilder.toString(); return stringBuilder.toString();
} }
} }

View File

@ -25,109 +25,109 @@ import org.jboss.netty.handler.codec.replay.VoidEnum;
/** /**
* Decodes {@link ChannelBuffer}s into {@link WebSocketFrame}s. * Decodes {@link ChannelBuffer}s into {@link WebSocketFrame}s.
* <p> * <p>
* For the detailed instruction on adding add Web Socket support to your HTTP * For the detailed instruction on adding add Web Socket support to your HTTP server, take a look into the
* server, take a look into the <tt>WebSocketServer</tt> example located in the * <tt>WebSocketServer</tt> example located in the {@code org.jboss.netty.example.http.websocket} package.
* {@code org.jboss.netty.example.http.websocket} package. *
* @apiviz.landmark * @apiviz.landmark
* @apiviz.uses org.jboss.netty.handler.codec.http.websocket.WebSocketFrame * @apiviz.uses org.jboss.netty.handler.codec.http.websocket.WebSocketFrame
*/ */
public class WebSocket00FrameDecoder extends ReplayingDecoder<VoidEnum> { public class WebSocket00FrameDecoder extends ReplayingDecoder<VoidEnum> {
public static final int DEFAULT_MAX_FRAME_SIZE = 16384; public static final int DEFAULT_MAX_FRAME_SIZE = 16384;
private final int maxFrameSize; private final int maxFrameSize;
private boolean receivedClosingHandshake; private boolean receivedClosingHandshake;
public WebSocket00FrameDecoder() { public WebSocket00FrameDecoder() {
this(DEFAULT_MAX_FRAME_SIZE); this(DEFAULT_MAX_FRAME_SIZE);
} }
/** /**
* Creates a new instance of {@code WebSocketFrameDecoder} with the * Creates a new instance of {@code WebSocketFrameDecoder} with the specified {@code maxFrameSize}. If the client
* specified {@code maxFrameSize}. If the client sends a frame size larger * sends a frame size larger than {@code maxFrameSize}, the channel will be closed.
* than {@code maxFrameSize}, the channel will be closed. *
* * @param maxFrameSize
* @param maxFrameSize * the maximum frame size to decode
* the maximum frame size to decode */
*/ public WebSocket00FrameDecoder(int maxFrameSize) {
public WebSocket00FrameDecoder(int maxFrameSize) { this.maxFrameSize = maxFrameSize;
this.maxFrameSize = maxFrameSize; }
}
@Override @Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, VoidEnum state) throws Exception { protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, VoidEnum state)
throws Exception {
// Discard all data received if closing handshake was received before. // Discard all data received if closing handshake was received before.
if (receivedClosingHandshake) { if (receivedClosingHandshake) {
buffer.skipBytes(actualReadableBytes()); buffer.skipBytes(actualReadableBytes());
return null; return null;
} }
// Decode a frame otherwise. // Decode a frame otherwise.
byte type = buffer.readByte(); byte type = buffer.readByte();
if ((type & 0x80) == 0x80) { if ((type & 0x80) == 0x80) {
// If the MSB on type is set, decode the frame length // If the MSB on type is set, decode the frame length
return decodeBinaryFrame(type, buffer); return decodeBinaryFrame(type, buffer);
} else { } else {
// Decode a 0xff terminated UTF-8 string // Decode a 0xff terminated UTF-8 string
return decodeTextFrame(type, buffer); return decodeTextFrame(type, buffer);
} }
} }
private WebSocketFrame decodeBinaryFrame(byte type, ChannelBuffer buffer) throws TooLongFrameException { private WebSocketFrame decodeBinaryFrame(byte type, ChannelBuffer buffer) throws TooLongFrameException {
long frameSize = 0; long frameSize = 0;
int lengthFieldSize = 0; int lengthFieldSize = 0;
byte b; byte b;
do { do {
b = buffer.readByte(); b = buffer.readByte();
frameSize <<= 7; frameSize <<= 7;
frameSize |= b & 0x7f; frameSize |= b & 0x7f;
if (frameSize > maxFrameSize) { if (frameSize > maxFrameSize) {
throw new TooLongFrameException(); throw new TooLongFrameException();
} }
lengthFieldSize++; lengthFieldSize++;
if (lengthFieldSize > 8) { if (lengthFieldSize > 8) {
// Perhaps a malicious peer? // Perhaps a malicious peer?
throw new TooLongFrameException(); throw new TooLongFrameException();
} }
} while ((b & 0x80) == 0x80); } while ((b & 0x80) == 0x80);
if (type == ((byte) 0xFF) && frameSize == 0) { if (type == ((byte) 0xFF) && frameSize == 0) {
receivedClosingHandshake = true; receivedClosingHandshake = true;
return new CloseWebSocketFrame(); return new CloseWebSocketFrame();
} }
return new BinaryWebSocketFrame(buffer.readBytes((int) frameSize)); return new BinaryWebSocketFrame(buffer.readBytes((int) frameSize));
} }
private WebSocketFrame decodeTextFrame(byte type, ChannelBuffer buffer) throws TooLongFrameException { private WebSocketFrame decodeTextFrame(byte type, ChannelBuffer buffer) throws TooLongFrameException {
int ridx = buffer.readerIndex(); int ridx = buffer.readerIndex();
int rbytes = actualReadableBytes(); int rbytes = actualReadableBytes();
int delimPos = buffer.indexOf(ridx, ridx + rbytes, (byte) 0xFF); int delimPos = buffer.indexOf(ridx, ridx + rbytes, (byte) 0xFF);
if (delimPos == -1) { if (delimPos == -1) {
// Frame delimiter (0xFF) not found // Frame delimiter (0xFF) not found
if (rbytes > maxFrameSize) { if (rbytes > maxFrameSize) {
// Frame length exceeded the maximum // Frame length exceeded the maximum
throw new TooLongFrameException(); throw new TooLongFrameException();
} else { } else {
// Wait until more data is received // Wait until more data is received
return null; return null;
} }
} }
int frameSize = delimPos - ridx; int frameSize = delimPos - ridx;
if (frameSize > maxFrameSize) { if (frameSize > maxFrameSize) {
throw new TooLongFrameException(); throw new TooLongFrameException();
} }
ChannelBuffer binaryData = buffer.readBytes(frameSize); ChannelBuffer binaryData = buffer.readBytes(frameSize);
buffer.skipBytes(1); buffer.skipBytes(1);
int ffDelimPos = binaryData.indexOf(binaryData.readerIndex(), binaryData.writerIndex(), (byte) 0xFF); int ffDelimPos = binaryData.indexOf(binaryData.readerIndex(), binaryData.writerIndex(), (byte) 0xFF);
if (ffDelimPos >= 0) { if (ffDelimPos >= 0) {
throw new IllegalArgumentException("a text frame should not contain 0xFF."); throw new IllegalArgumentException("a text frame should not contain 0xFF.");
} }
return new TextWebSocketFrame(binaryData); return new TextWebSocketFrame(binaryData);
} }
} }

View File

@ -24,73 +24,74 @@ import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
/** /**
* Encodes a {@link WebSocketFrame} into a {@link ChannelBuffer}. * Encodes a {@link WebSocketFrame} into a {@link ChannelBuffer}.
* <p> * <p>
* For the detailed instruction on adding add Web Socket support to your HTTP * For the detailed instruction on adding add Web Socket support to your HTTP server, take a look into the
* server, take a look into the <tt>WebSocketServer</tt> example located in the * <tt>WebSocketServer</tt> example located in the {@code org.jboss.netty.example.http.websocket} package.
* {@code org.jboss.netty.example.http.websocket} package. *
* @apiviz.landmark * @apiviz.landmark
* @apiviz.uses org.jboss.netty.handler.codec.http.websocket.WebSocketFrame * @apiviz.uses org.jboss.netty.handler.codec.http.websocket.WebSocketFrame
*/ */
@Sharable @Sharable
public class WebSocket00FrameEncoder extends OneToOneEncoder { public class WebSocket00FrameEncoder extends OneToOneEncoder {
@Override @Override
protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
if (msg instanceof WebSocketFrame) { if (msg instanceof WebSocketFrame) {
WebSocketFrame frame = (WebSocketFrame) msg; WebSocketFrame frame = (WebSocketFrame) msg;
if (frame instanceof TextWebSocketFrame) { if (frame instanceof TextWebSocketFrame) {
// Text frame // Text frame
ChannelBuffer data = frame.getBinaryData(); ChannelBuffer data = frame.getBinaryData();
ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), data.readableBytes() + 2); ChannelBuffer encoded = channel.getConfig().getBufferFactory()
encoded.writeByte((byte) 0x00); .getBuffer(data.order(), data.readableBytes() + 2);
encoded.writeBytes(data, data.readerIndex(), data.readableBytes()); encoded.writeByte((byte) 0x00);
encoded.writeByte((byte) 0xFF); encoded.writeBytes(data, data.readerIndex(), data.readableBytes());
return encoded; encoded.writeByte((byte) 0xFF);
} else if (frame instanceof CloseWebSocketFrame) { return encoded;
// Close frame } else if (frame instanceof CloseWebSocketFrame) {
ChannelBuffer data = frame.getBinaryData(); // Close frame
ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), 2); ChannelBuffer data = frame.getBinaryData();
encoded.writeByte((byte) 0xFF); ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), 2);
encoded.writeByte((byte) 0x00); encoded.writeByte((byte) 0xFF);
return encoded; encoded.writeByte((byte) 0x00);
} else { return encoded;
// Binary frame } else {
ChannelBuffer data = frame.getBinaryData(); // Binary frame
int dataLen = data.readableBytes(); ChannelBuffer data = frame.getBinaryData();
ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), dataLen + 5); int dataLen = data.readableBytes();
ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), dataLen + 5);
// Encode type. // Encode type.
encoded.writeByte((byte) 0x80); encoded.writeByte((byte) 0x80);
// Encode length. // Encode length.
int b1 = dataLen >>> 28 & 0x7F; int b1 = dataLen >>> 28 & 0x7F;
int b2 = dataLen >>> 14 & 0x7F; int b2 = dataLen >>> 14 & 0x7F;
int b3 = dataLen >>> 7 & 0x7F; int b3 = dataLen >>> 7 & 0x7F;
int b4 = dataLen & 0x7F; int b4 = dataLen & 0x7F;
if (b1 == 0) { if (b1 == 0) {
if (b2 == 0) { if (b2 == 0) {
if (b3 == 0) { if (b3 == 0) {
encoded.writeByte(b4); encoded.writeByte(b4);
} else { } else {
encoded.writeByte(b3 | 0x80); encoded.writeByte(b3 | 0x80);
encoded.writeByte(b4); encoded.writeByte(b4);
} }
} else { } else {
encoded.writeByte(b2 | 0x80); encoded.writeByte(b2 | 0x80);
encoded.writeByte(b3 | 0x80); encoded.writeByte(b3 | 0x80);
encoded.writeByte(b4); encoded.writeByte(b4);
} }
} else { } else {
encoded.writeByte(b1 | 0x80); encoded.writeByte(b1 | 0x80);
encoded.writeByte(b2 | 0x80); encoded.writeByte(b2 | 0x80);
encoded.writeByte(b3 | 0x80); encoded.writeByte(b3 | 0x80);
encoded.writeByte(b4); encoded.writeByte(b4);
} }
// Encode binary data. // Encode binary data.
encoded.writeBytes(data, data.readerIndex(), dataLen); encoded.writeBytes(data, data.readerIndex(), dataLen);
return encoded; return encoded;
} }
} }
return msg; return msg;
} }
} }

View File

@ -50,331 +50,330 @@ import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory; import org.jboss.netty.logging.InternalLoggerFactory;
/** /**
* Decodes a web socket frame from wire protocol version 8 format. This code was * Decodes a web socket frame from wire protocol version 8 format. This code was forked from <a
* forked from <a href="https://github.com/joewalnes/webbit">webbit</a> and * href="https://github.com/joewalnes/webbit">webbit</a> and modified.
* modified.
*/ */
public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDecoder.State> { public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDecoder.State> {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameDecoder.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameDecoder.class);
private static final byte OPCODE_CONT = 0x0; private static final byte OPCODE_CONT = 0x0;
private static final byte OPCODE_TEXT = 0x1; private static final byte OPCODE_TEXT = 0x1;
private static final byte OPCODE_BINARY = 0x2; private static final byte OPCODE_BINARY = 0x2;
private static final byte OPCODE_CLOSE = 0x8; private static final byte OPCODE_CLOSE = 0x8;
private static final byte OPCODE_PING = 0x9; private static final byte OPCODE_PING = 0x9;
private static final byte OPCODE_PONG = 0xA; private static final byte OPCODE_PONG = 0xA;
private UTF8Output fragmentedFramesText = null; private UTF8Output fragmentedFramesText = null;
private int fragmentedFramesCount = 0; private int fragmentedFramesCount = 0;
private boolean frameFinalFlag; private boolean frameFinalFlag;
private int frameRsv; private int frameRsv;
private int frameOpcode; private int frameOpcode;
private long framePayloadLength; private long framePayloadLength;
private ChannelBuffer framePayload = null; private ChannelBuffer framePayload = null;
private int framePayloadBytesRead = 0; private int framePayloadBytesRead = 0;
private ChannelBuffer maskingKey; private ChannelBuffer maskingKey;
private boolean allowExtensions = false; private boolean allowExtensions = false;
private boolean maskedPayload = false; private boolean maskedPayload = false;
private boolean receivedClosingHandshake = false; private boolean receivedClosingHandshake = false;
public enum State { public enum State {
FRAME_START, MASKING_KEY, PAYLOAD, CORRUPT FRAME_START, MASKING_KEY, PAYLOAD, CORRUPT
} }
/** /**
* Constructor * Constructor
* *
* @param maskedPayload * @param maskedPayload
* Web socket servers must set this to true processed incoming * Web socket servers must set this to true processed incoming masked payload. Client implementations
* masked payload. Client implementations must set this to false. * must set this to false.
* @param allowExtensions * @param allowExtensions
* Flag to allow reserved extension bits to be used or not * Flag to allow reserved extension bits to be used or not
*/ */
public WebSocket08FrameDecoder(boolean maskedPayload, boolean allowExtensions) { public WebSocket08FrameDecoder(boolean maskedPayload, boolean allowExtensions) {
super(State.FRAME_START); super(State.FRAME_START);
this.maskedPayload = maskedPayload; this.maskedPayload = maskedPayload;
this.allowExtensions = allowExtensions; this.allowExtensions = allowExtensions;
} }
@Override @Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, State state) protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, State state)
throws Exception { throws Exception {
// Discard all data received if closing handshake was received before. // Discard all data received if closing handshake was received before.
if (receivedClosingHandshake) { if (receivedClosingHandshake) {
buffer.skipBytes(actualReadableBytes()); buffer.skipBytes(actualReadableBytes());
return null; return null;
} }
switch (state) { switch (state) {
case FRAME_START: case FRAME_START:
framePayloadBytesRead = 0; framePayloadBytesRead = 0;
framePayloadLength = -1; framePayloadLength = -1;
framePayload = null; framePayload = null;
// FIN, RSV, OPCODE // FIN, RSV, OPCODE
byte b = buffer.readByte(); byte b = buffer.readByte();
frameFinalFlag = (b & 0x80) != 0; frameFinalFlag = (b & 0x80) != 0;
frameRsv = (b & 0x70) >> 4; frameRsv = (b & 0x70) >> 4;
frameOpcode = (b & 0x0F); frameOpcode = (b & 0x0F);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Decoding WebSocket Frame opCode=" + frameOpcode); logger.debug("Decoding WebSocket Frame opCode=" + frameOpcode);
} }
// MASK, PAYLOAD LEN 1 // MASK, PAYLOAD LEN 1
b = buffer.readByte(); b = buffer.readByte();
boolean frameMasked = (b & 0x80) != 0; boolean frameMasked = (b & 0x80) != 0;
int framePayloadLen1 = (b & 0x7F); int framePayloadLen1 = (b & 0x7F);
if (frameRsv != 0 && !this.allowExtensions) { if (frameRsv != 0 && !this.allowExtensions) {
protocolViolation(channel, "RSV != 0 and no extension negotiated, RSV:" + frameRsv); protocolViolation(channel, "RSV != 0 and no extension negotiated, RSV:" + frameRsv);
return null; return null;
} }
if (this.maskedPayload && !frameMasked) { if (this.maskedPayload && !frameMasked) {
protocolViolation(channel, "unmasked client to server frame"); protocolViolation(channel, "unmasked client to server frame");
return null; return null;
} }
if (frameOpcode > 7) { // control frame (have MSB in opcode set) if (frameOpcode > 7) { // control frame (have MSB in opcode set)
// control frames MUST NOT be fragmented // control frames MUST NOT be fragmented
if (!frameFinalFlag) { if (!frameFinalFlag) {
protocolViolation(channel, "fragmented control frame"); protocolViolation(channel, "fragmented control frame");
return null; return null;
} }
// control frames MUST have payload 125 octets or less // control frames MUST have payload 125 octets or less
if (framePayloadLen1 > 125) { if (framePayloadLen1 > 125) {
protocolViolation(channel, "control frame with payload length > 125 octets"); protocolViolation(channel, "control frame with payload length > 125 octets");
return null; return null;
} }
// check for reserved control frame opcodes // check for reserved control frame opcodes
if (!(frameOpcode == OPCODE_CLOSE || frameOpcode == OPCODE_PING || frameOpcode == OPCODE_PONG)) { if (!(frameOpcode == OPCODE_CLOSE || frameOpcode == OPCODE_PING || frameOpcode == OPCODE_PONG)) {
protocolViolation(channel, "control frame using reserved opcode " + frameOpcode); protocolViolation(channel, "control frame using reserved opcode " + frameOpcode);
return null; return null;
} }
// close frame : if there is a body, the first two bytes of the // close frame : if there is a body, the first two bytes of the
// body MUST be a 2-byte unsigned integer (in network byte // body MUST be a 2-byte unsigned integer (in network byte
// order) representing a status code // order) representing a status code
if (frameOpcode == 8 && framePayloadLen1 == 1) { if (frameOpcode == 8 && framePayloadLen1 == 1) {
protocolViolation(channel, "received close control frame with payload len 1"); protocolViolation(channel, "received close control frame with payload len 1");
return null; return null;
} }
} else { // data frame } else { // data frame
// check for reserved data frame opcodes // check for reserved data frame opcodes
if (!(frameOpcode == OPCODE_CONT || frameOpcode == OPCODE_TEXT || frameOpcode == OPCODE_BINARY)) { if (!(frameOpcode == OPCODE_CONT || frameOpcode == OPCODE_TEXT || frameOpcode == OPCODE_BINARY)) {
protocolViolation(channel, "data frame using reserved opcode " + frameOpcode); protocolViolation(channel, "data frame using reserved opcode " + frameOpcode);
return null; return null;
} }
// check opcode vs message fragmentation state 1/2 // check opcode vs message fragmentation state 1/2
if (fragmentedFramesCount == 0 && frameOpcode == OPCODE_CONT) { if (fragmentedFramesCount == 0 && frameOpcode == OPCODE_CONT) {
protocolViolation(channel, "received continuation data frame outside fragmented message"); protocolViolation(channel, "received continuation data frame outside fragmented message");
return null; return null;
} }
// check opcode vs message fragmentation state 2/2 // check opcode vs message fragmentation state 2/2
if (fragmentedFramesCount != 0 && frameOpcode != OPCODE_CONT && frameOpcode != OPCODE_PING) { if (fragmentedFramesCount != 0 && frameOpcode != OPCODE_CONT && frameOpcode != OPCODE_PING) {
protocolViolation(channel, "received non-continuation data frame while inside fragmented message"); protocolViolation(channel, "received non-continuation data frame while inside fragmented message");
return null; return null;
} }
} }
// Read frame payload length // Read frame payload length
if (framePayloadLen1 == 126) { if (framePayloadLen1 == 126) {
framePayloadLength = buffer.readUnsignedShort(); framePayloadLength = buffer.readUnsignedShort();
if (framePayloadLength < 126) { if (framePayloadLength < 126) {
protocolViolation(channel, "invalid data frame length (not using minimal length encoding)"); protocolViolation(channel, "invalid data frame length (not using minimal length encoding)");
return null; return null;
} }
} else if (framePayloadLen1 == 127) { } else if (framePayloadLen1 == 127) {
framePayloadLength = buffer.readLong(); framePayloadLength = buffer.readLong();
// TODO: check if it's bigger than 0x7FFFFFFFFFFFFFFF, Maybe // TODO: check if it's bigger than 0x7FFFFFFFFFFFFFFF, Maybe
// just check if it's negative? // just check if it's negative?
if (framePayloadLength < 65536) { if (framePayloadLength < 65536) {
protocolViolation(channel, "invalid data frame length (not using minimal length encoding)"); protocolViolation(channel, "invalid data frame length (not using minimal length encoding)");
return null; return null;
} }
} else { } else {
framePayloadLength = framePayloadLen1; framePayloadLength = framePayloadLen1;
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Decoding WebSocket Frame length=" + framePayloadLength); logger.debug("Decoding WebSocket Frame length=" + framePayloadLength);
} }
checkpoint(State.MASKING_KEY); checkpoint(State.MASKING_KEY);
case MASKING_KEY: case MASKING_KEY:
if (this.maskedPayload) { if (this.maskedPayload) {
maskingKey = buffer.readBytes(4); maskingKey = buffer.readBytes(4);
} }
checkpoint(State.PAYLOAD); checkpoint(State.PAYLOAD);
case PAYLOAD: case PAYLOAD:
// Sometimes, the payload may not be delivered in 1 nice packet // Sometimes, the payload may not be delivered in 1 nice packet
// We need to accumulate the data until we have it all // We need to accumulate the data until we have it all
int rbytes = actualReadableBytes(); int rbytes = actualReadableBytes();
ChannelBuffer payloadBuffer = null; ChannelBuffer payloadBuffer = null;
int willHaveReadByteCount = framePayloadBytesRead + rbytes; int willHaveReadByteCount = framePayloadBytesRead + rbytes;
// logger.debug("Frame rbytes=" + rbytes + " willHaveReadByteCount=" // logger.debug("Frame rbytes=" + rbytes + " willHaveReadByteCount="
// + willHaveReadByteCount + " framePayloadLength=" + // + willHaveReadByteCount + " framePayloadLength=" +
// framePayloadLength); // framePayloadLength);
if (willHaveReadByteCount == framePayloadLength) { if (willHaveReadByteCount == framePayloadLength) {
// We have all our content so proceed to process // We have all our content so proceed to process
payloadBuffer = buffer.readBytes(rbytes); payloadBuffer = buffer.readBytes(rbytes);
} else if (willHaveReadByteCount < framePayloadLength) { } else if (willHaveReadByteCount < framePayloadLength) {
// We don't have all our content so accumulate payload. // We don't have all our content so accumulate payload.
// Returning null means we will get called back // Returning null means we will get called back
payloadBuffer = buffer.readBytes(rbytes); payloadBuffer = buffer.readBytes(rbytes);
if (framePayload == null) { if (framePayload == null) {
framePayload = channel.getConfig().getBufferFactory().getBuffer(toFrameLength(framePayloadLength)); framePayload = channel.getConfig().getBufferFactory().getBuffer(toFrameLength(framePayloadLength));
} }
framePayload.writeBytes(payloadBuffer); framePayload.writeBytes(payloadBuffer);
framePayloadBytesRead = framePayloadBytesRead + rbytes; framePayloadBytesRead = framePayloadBytesRead + rbytes;
// Return null to wait for more bytes to arrive // Return null to wait for more bytes to arrive
return null; return null;
} else if (willHaveReadByteCount > framePayloadLength) { } else if (willHaveReadByteCount > framePayloadLength) {
// We have more than what we need so read up to the end of frame // We have more than what we need so read up to the end of frame
// Leave the remainder in the buffer for next frame // Leave the remainder in the buffer for next frame
payloadBuffer = buffer.readBytes(toFrameLength(framePayloadLength - framePayloadBytesRead)); payloadBuffer = buffer.readBytes(toFrameLength(framePayloadLength - framePayloadBytesRead));
} }
// Now we have all the data, the next checkpoint must be the next // Now we have all the data, the next checkpoint must be the next
// frame // frame
checkpoint(State.FRAME_START); checkpoint(State.FRAME_START);
// Take the data that we have in this packet // Take the data that we have in this packet
if (framePayload == null) { if (framePayload == null) {
framePayload = payloadBuffer; framePayload = payloadBuffer;
} else { } else {
framePayload.writeBytes(payloadBuffer); framePayload.writeBytes(payloadBuffer);
} }
// Unmask data if needed // Unmask data if needed
if (this.maskedPayload) { if (this.maskedPayload) {
unmask(framePayload); unmask(framePayload);
} }
// Processing ping/pong/close frames because they cannot be // Processing ping/pong/close frames because they cannot be
// fragmented // fragmented
if (frameOpcode == OPCODE_PING) { if (frameOpcode == OPCODE_PING) {
return new PingWebSocketFrame(frameFinalFlag, frameRsv, framePayload); return new PingWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
} else if (frameOpcode == OPCODE_PONG) { } else if (frameOpcode == OPCODE_PONG) {
return new PongWebSocketFrame(frameFinalFlag, frameRsv, framePayload); return new PongWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
} else if (frameOpcode == OPCODE_CLOSE) { } else if (frameOpcode == OPCODE_CLOSE) {
this.receivedClosingHandshake = true; this.receivedClosingHandshake = true;
return new CloseWebSocketFrame(frameFinalFlag, frameRsv); return new CloseWebSocketFrame(frameFinalFlag, frameRsv);
} }
// Processing for possible fragmented messages for text and binary // Processing for possible fragmented messages for text and binary
// frames // frames
String aggregatedText = null; String aggregatedText = null;
if (frameFinalFlag) { if (frameFinalFlag) {
// Final frame of the sequence. Apparently ping frames are // Final frame of the sequence. Apparently ping frames are
// allowed in the middle of a fragmented message // allowed in the middle of a fragmented message
if (frameOpcode != OPCODE_PING) { if (frameOpcode != OPCODE_PING) {
fragmentedFramesCount = 0; fragmentedFramesCount = 0;
// Check text for UTF8 correctness // Check text for UTF8 correctness
if (frameOpcode == OPCODE_TEXT || fragmentedFramesText != null) { if (frameOpcode == OPCODE_TEXT || fragmentedFramesText != null) {
// Check UTF-8 correctness for this payload // Check UTF-8 correctness for this payload
checkUTF8String(channel, framePayload.array()); checkUTF8String(channel, framePayload.array());
// This does a second check to make sure UTF-8 // This does a second check to make sure UTF-8
// correctness for entire text message // correctness for entire text message
aggregatedText = fragmentedFramesText.toString(); aggregatedText = fragmentedFramesText.toString();
fragmentedFramesText = null; fragmentedFramesText = null;
} }
} }
} else { } else {
// Not final frame so we can expect more frames in the // Not final frame so we can expect more frames in the
// fragmented sequence // fragmented sequence
if (fragmentedFramesCount == 0) { if (fragmentedFramesCount == 0) {
// First text or binary frame for a fragmented set // First text or binary frame for a fragmented set
fragmentedFramesText = null; fragmentedFramesText = null;
if (frameOpcode == OPCODE_TEXT) { if (frameOpcode == OPCODE_TEXT) {
checkUTF8String(channel, framePayload.array()); checkUTF8String(channel, framePayload.array());
} }
} else { } else {
// Subsequent frames - only check if init frame is text // Subsequent frames - only check if init frame is text
if (fragmentedFramesText != null) { if (fragmentedFramesText != null) {
checkUTF8String(channel, framePayload.array()); checkUTF8String(channel, framePayload.array());
} }
} }
// Increment counter // Increment counter
fragmentedFramesCount++; fragmentedFramesCount++;
} }
// Return the frame // Return the frame
if (frameOpcode == OPCODE_TEXT) { if (frameOpcode == OPCODE_TEXT) {
return new TextWebSocketFrame(frameFinalFlag, frameRsv, framePayload); return new TextWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
} else if (frameOpcode == OPCODE_BINARY) { } else if (frameOpcode == OPCODE_BINARY) {
return new BinaryWebSocketFrame(frameFinalFlag, frameRsv, framePayload); return new BinaryWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
} else if (frameOpcode == OPCODE_CONT) { } else if (frameOpcode == OPCODE_CONT) {
return new ContinuationWebSocketFrame(frameFinalFlag, frameRsv, framePayload, aggregatedText); return new ContinuationWebSocketFrame(frameFinalFlag, frameRsv, framePayload, aggregatedText);
} else { } else {
throw new UnsupportedOperationException("Cannot decode web socket frame with opcode: " + frameOpcode); throw new UnsupportedOperationException("Cannot decode web socket frame with opcode: " + frameOpcode);
} }
case CORRUPT: case CORRUPT:
// If we don't keep reading Netty will throw an exception saying // If we don't keep reading Netty will throw an exception saying
// we can't return null if no bytes read and state not changed. // we can't return null if no bytes read and state not changed.
buffer.readByte(); buffer.readByte();
return null; return null;
default: default:
throw new Error("Shouldn't reach here."); throw new Error("Shouldn't reach here.");
} }
} }
private void unmask(ChannelBuffer frame) { private void unmask(ChannelBuffer frame) {
byte[] bytes = frame.array(); byte[] bytes = frame.array();
for (int i = 0; i < bytes.length; i++) { for (int i = 0; i < bytes.length; i++) {
frame.setByte(i, frame.getByte(i) ^ maskingKey.getByte(i % 4)); frame.setByte(i, frame.getByte(i) ^ maskingKey.getByte(i % 4));
} }
} }
private void protocolViolation(Channel channel, String reason) throws CorruptedFrameException { private void protocolViolation(Channel channel, String reason) throws CorruptedFrameException {
checkpoint(State.CORRUPT); checkpoint(State.CORRUPT);
if (channel.isConnected()) { if (channel.isConnected()) {
channel.write(ChannelBuffers.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); channel.write(ChannelBuffers.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
channel.close().awaitUninterruptibly(); channel.close().awaitUninterruptibly();
} }
throw new CorruptedFrameException(reason); throw new CorruptedFrameException(reason);
} }
private int toFrameLength(long l) throws TooLongFrameException { private int toFrameLength(long l) throws TooLongFrameException {
if (l > Integer.MAX_VALUE) { if (l > Integer.MAX_VALUE) {
throw new TooLongFrameException("Length:" + l); throw new TooLongFrameException("Length:" + l);
} else { } else {
return (int) l; return (int) l;
} }
} }
private void checkUTF8String(Channel channel, byte[] bytes) throws CorruptedFrameException { private void checkUTF8String(Channel channel, byte[] bytes) throws CorruptedFrameException {
try { try {
// StringBuilder sb = new StringBuilder("UTF8 " + bytes.length + // StringBuilder sb = new StringBuilder("UTF8 " + bytes.length +
// " bytes: "); // " bytes: ");
// for (byte b : bytes) { // for (byte b : bytes) {
// sb.append(Integer.toHexString(b)).append(" "); // sb.append(Integer.toHexString(b)).append(" ");
// } // }
// logger.debug(sb.toString()); // logger.debug(sb.toString());
if (fragmentedFramesText == null) { if (fragmentedFramesText == null) {
fragmentedFramesText = new UTF8Output(bytes); fragmentedFramesText = new UTF8Output(bytes);
} else { } else {
fragmentedFramesText.write(bytes); fragmentedFramesText.write(bytes);
} }
} catch (UTF8Exception ex) { } catch (UTF8Exception ex) {
protocolViolation(channel, "invalid UTF-8 bytes"); protocolViolation(channel, "invalid UTF-8 bytes");
} }
} }
} }

View File

@ -51,123 +51,123 @@ import org.jboss.netty.logging.InternalLoggerFactory;
/** /**
* <p> * <p>
* Encodes a web socket frame into wire protocol version 8 format. This code was * Encodes a web socket frame into wire protocol version 8 format. This code was forked from <a
* forked from <a href="https://github.com/joewalnes/webbit">webbit</a> and * href="https://github.com/joewalnes/webbit">webbit</a> and modified.
* modified.
* </p> * </p>
*/ */
public class WebSocket08FrameEncoder extends OneToOneEncoder { public class WebSocket08FrameEncoder extends OneToOneEncoder {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameEncoder.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameEncoder.class);
private static final byte OPCODE_CONT = 0x0; private static final byte OPCODE_CONT = 0x0;
private static final byte OPCODE_TEXT = 0x1; private static final byte OPCODE_TEXT = 0x1;
private static final byte OPCODE_BINARY = 0x2; private static final byte OPCODE_BINARY = 0x2;
private static final byte OPCODE_CLOSE = 0x8; private static final byte OPCODE_CLOSE = 0x8;
private static final byte OPCODE_PING = 0x9; private static final byte OPCODE_PING = 0x9;
private static final byte OPCODE_PONG = 0xA; private static final byte OPCODE_PONG = 0xA;
private boolean maskPayload = false; private boolean maskPayload = false;
/** /**
* Constructor * Constructor
* *
* @param maskPayload * @param maskPayload
* Web socket clients must set this to true to mask payload. * Web socket clients must set this to true to mask payload. Server implementations must set this to
* Server implementations must set this to false. * false.
*/ */
public WebSocket08FrameEncoder(boolean maskPayload) { public WebSocket08FrameEncoder(boolean maskPayload) {
this.maskPayload = maskPayload; this.maskPayload = maskPayload;
} }
@Override @Override
protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
byte[] mask = null; byte[] mask = null;
if (msg instanceof WebSocketFrame) { if (msg instanceof WebSocketFrame) {
WebSocketFrame frame = (WebSocketFrame) msg; WebSocketFrame frame = (WebSocketFrame) msg;
ChannelBuffer data = frame.getBinaryData(); ChannelBuffer data = frame.getBinaryData();
if (data == null) { if (data == null) {
data = ChannelBuffers.EMPTY_BUFFER; data = ChannelBuffers.EMPTY_BUFFER;
} }
byte opcode; byte opcode;
if (frame instanceof TextWebSocketFrame) { if (frame instanceof TextWebSocketFrame) {
opcode = OPCODE_TEXT; opcode = OPCODE_TEXT;
} else if (frame instanceof PingWebSocketFrame) { } else if (frame instanceof PingWebSocketFrame) {
opcode = OPCODE_PING; opcode = OPCODE_PING;
} else if (frame instanceof PongWebSocketFrame) { } else if (frame instanceof PongWebSocketFrame) {
opcode = OPCODE_PONG; opcode = OPCODE_PONG;
} else if (frame instanceof CloseWebSocketFrame) { } else if (frame instanceof CloseWebSocketFrame) {
opcode = OPCODE_CLOSE; opcode = OPCODE_CLOSE;
} else if (frame instanceof BinaryWebSocketFrame) { } else if (frame instanceof BinaryWebSocketFrame) {
opcode = OPCODE_BINARY; opcode = OPCODE_BINARY;
} else if (frame instanceof ContinuationWebSocketFrame) { } else if (frame instanceof ContinuationWebSocketFrame) {
opcode = OPCODE_CONT; opcode = OPCODE_CONT;
} else { } else {
throw new UnsupportedOperationException("Cannot encode frame of type: " + frame.getClass().getName()); throw new UnsupportedOperationException("Cannot encode frame of type: " + frame.getClass().getName());
} }
int length = data.readableBytes(); int length = data.readableBytes();
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Encoding WebSocket Frame opCode=" + opcode + " length=" + length); logger.debug("Encoding WebSocket Frame opCode=" + opcode + " length=" + length);
} }
int b0 = 0;
if (frame.isFinalFragment()) {
b0 |= (1 << 7);
}
b0 |= (frame.getRsv() % 8) << 4;
b0 |= opcode % 128;
ChannelBuffer header; int b0 = 0;
ChannelBuffer body; if (frame.isFinalFragment()) {
b0 |= (1 << 7);
}
b0 |= (frame.getRsv() % 8) << 4;
b0 |= opcode % 128;
if (opcode == OPCODE_PING && length > 125) { ChannelBuffer header;
throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was " + length); ChannelBuffer body;
}
int maskLength = this.maskPayload ? 4 : 0; if (opcode == OPCODE_PING && length > 125) {
if (length <= 125) { throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was "
header = ChannelBuffers.buffer(2 + maskLength); + length);
header.writeByte(b0); }
byte b = (byte) (this.maskPayload ? (0x80 | (byte) length) : (byte) length);
header.writeByte(b);
} else if (length <= 0xFFFF) {
header = ChannelBuffers.buffer(4 + maskLength);
header.writeByte(b0);
header.writeByte(this.maskPayload ? (0xFE) : 126);
header.writeByte((length >>> 8) & 0xFF);
header.writeByte((length) & 0xFF);
} else {
header = ChannelBuffers.buffer(10 + maskLength);
header.writeByte(b0);
header.writeByte(this.maskPayload ? (0xFF) : 127);
header.writeLong(length);
}
// Write payload int maskLength = this.maskPayload ? 4 : 0;
if (this.maskPayload) { if (length <= 125) {
Integer random = (int) (Math.random() * Integer.MAX_VALUE); header = ChannelBuffers.buffer(2 + maskLength);
mask = ByteBuffer.allocate(4).putInt(random).array(); header.writeByte(b0);
header.writeBytes(mask); byte b = (byte) (this.maskPayload ? (0x80 | (byte) length) : (byte) length);
header.writeByte(b);
} else if (length <= 0xFFFF) {
header = ChannelBuffers.buffer(4 + maskLength);
header.writeByte(b0);
header.writeByte(this.maskPayload ? (0xFE) : 126);
header.writeByte((length >>> 8) & 0xFF);
header.writeByte((length) & 0xFF);
} else {
header = ChannelBuffers.buffer(10 + maskLength);
header.writeByte(b0);
header.writeByte(this.maskPayload ? (0xFF) : 127);
header.writeLong(length);
}
body = ChannelBuffers.buffer(length); // Write payload
int counter = 0; if (this.maskPayload) {
while (data.readableBytes() > 0) { Integer random = (int) (Math.random() * Integer.MAX_VALUE);
byte byteData = data.readByte(); mask = ByteBuffer.allocate(4).putInt(random).array();
body.writeByte(byteData ^ mask[+counter++ % 4]); header.writeBytes(mask);
}
} else {
body = data;
}
return ChannelBuffers.wrappedBuffer(header, body);
}
// If not websocket, then just return the message body = ChannelBuffers.buffer(length);
return msg; int counter = 0;
} while (data.readableBytes() > 0) {
byte byteData = data.readByte();
body.writeByte(byteData ^ mask[+counter++ % 4]);
}
} else {
body = data;
}
return ChannelBuffers.wrappedBuffer(header, body);
}
// If not websocket, then just return the message
return msg;
}
} }

View File

@ -39,21 +39,20 @@
package org.jboss.netty.handler.codec.http.websocketx; package org.jboss.netty.handler.codec.http.websocketx;
/** /**
* Decodes a web socket frame from wire protocol version 13 format. * Decodes a web socket frame from wire protocol version 13 format. V13 is essentially the same as V8.
* V13 is essentially the same as V8.
*/ */
public class WebSocket13FrameDecoder extends WebSocket08FrameDecoder { public class WebSocket13FrameDecoder extends WebSocket08FrameDecoder {
/** /**
* Constructor * Constructor
* *
* @param maskedPayload * @param maskedPayload
* Web socket servers must set this to true processed incoming * Web socket servers must set this to true processed incoming masked payload. Client implementations
* masked payload. Client implementations must set this to false. * must set this to false.
* @param allowExtensions * @param allowExtensions
* Flag to allow reserved extension bits to be used or not * Flag to allow reserved extension bits to be used or not
*/ */
public WebSocket13FrameDecoder(boolean maskedPayload, boolean allowExtensions) { public WebSocket13FrameDecoder(boolean maskedPayload, boolean allowExtensions) {
super(maskedPayload, allowExtensions); super(maskedPayload, allowExtensions);
} }
} }

View File

@ -38,23 +38,21 @@
package org.jboss.netty.handler.codec.http.websocketx; package org.jboss.netty.handler.codec.http.websocketx;
/** /**
* <p> * <p>
* Encodes a web socket frame into wire protocol version 13 format. V13 is essentially the same * Encodes a web socket frame into wire protocol version 13 format. V13 is essentially the same as V8.
* as V8.
* </p> * </p>
*/ */
public class WebSocket13FrameEncoder extends WebSocket08FrameEncoder { public class WebSocket13FrameEncoder extends WebSocket08FrameEncoder {
/** /**
* Constructor * Constructor
* *
* @param maskPayload * @param maskPayload
* Web socket clients must set this to true to mask payload. * Web socket clients must set this to true to mask payload. Server implementations must set this to
* Server implementations must set this to false. * false.
*/ */
public WebSocket13FrameEncoder(boolean maskPayload) { public WebSocket13FrameEncoder(boolean maskPayload) {
super(maskPayload); super(maskPayload);
} }
} }

View File

@ -48,12 +48,10 @@ public abstract class WebSocketClientHandshaker {
* Base constructor * Base constructor
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param version * @param version
* Version of web socket specification to use to connect to the * Version of web socket specification to use to connect to the server
* server
* @param subProtocol * @param subProtocol
* Sub protocol request sent to the server. * Sub protocol request sent to the server.
* @param customHeaders * @param customHeaders
@ -101,8 +99,7 @@ public abstract class WebSocketClientHandshaker {
} }
/** /**
* Returns the sub protocol request sent to the server as specified in the * Returns the sub protocol request sent to the server as specified in the constructor
* constructor
*/ */
public String getSubProtocolRequest() { public String getSubProtocolRequest() {
return subProtocolRequest; return subProtocolRequest;
@ -113,8 +110,7 @@ public abstract class WebSocketClientHandshaker {
} }
/** /**
* Returns the sub protocol response and sent by the server. Only available * Returns the sub protocol response and sent by the server. Only available after end of handshake.
* after end of handshake.
*/ */
public String getSubProtocolResponse() { public String getSubProtocolResponse() {
return subProtocolResponse; return subProtocolResponse;

View File

@ -35,10 +35,9 @@ import org.jboss.netty.handler.codec.http.HttpVersion;
/** /**
* <p> * <p>
* Performs client side opening and closing handshakes for web socket * Performs client side opening and closing handshakes for web socket specification version <a
* specification version <a * href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00" >draft-ietf-hybi-thewebsocketprotocol-
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00" * 00</a>
* >draft-ietf-hybi-thewebsocketprotocol- 00</a>
* </p> * </p>
* <p> * <p>
* A very large portion of this code was taken from the Netty 3.2 HTTP example. * A very large portion of this code was taken from the Netty 3.2 HTTP example.
@ -49,16 +48,13 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
private byte[] expectedChallengeResponseBytes = null; private byte[] expectedChallengeResponseBytes = null;
/** /**
* Constructor specifying the destination web socket location and version to * Constructor specifying the destination web socket location and version to initiate
* initiate
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param version * @param version
* Version of web socket specification to use to connect to the * Version of web socket specification to use to connect to the server
* server
* @param subProtocol * @param subProtocol
* Sub protocol request sent to the server. * Sub protocol request sent to the server.
* @param customHeaders * @param customHeaders
@ -177,8 +173,7 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
* @param channel * @param channel
* Channel * Channel
* @param response * @param response
* HTTP response returned from the server for the request sent by * HTTP response returned from the server for the request sent by beginOpeningHandshake00().
* beginOpeningHandshake00().
* @throws WebSocketHandshakeException * @throws WebSocketHandshakeException
*/ */
@Override @Override

View File

@ -35,10 +35,9 @@ import org.jboss.netty.util.CharsetUtil;
/** /**
* <p> * <p>
* Performs client side opening and closing handshakes for web socket * Performs client side opening and closing handshakes for web socket specification version <a
* specification version <a * href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10" >draft-ietf-hybi-thewebsocketprotocol-
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10" * 10</a>
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
* </p> * </p>
*/ */
public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
@ -54,21 +53,17 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
private boolean allowExtensions = false; private boolean allowExtensions = false;
/** /**
* Constructor specifying the destination web socket location and version to * Constructor specifying the destination web socket location and version to initiate
* initiate
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param version * @param version
* Version of web socket specification to use to connect to the * Version of web socket specification to use to connect to the server
* server
* @param subProtocol * @param subProtocol
* Sub protocol request sent to the server. * Sub protocol request sent to the server.
* @param allowExtensions * @param allowExtensions
* Allow extensions to be used in the reserved bits of the web * Allow extensions to be used in the reserved bits of the web socket frame
* socket frame
* @param customHeaders * @param customHeaders
* Map of custom headers to add to the client request * Map of custom headers to add to the client request
*/ */
@ -158,8 +153,7 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
* @param channel * @param channel
* Channel * Channel
* @param response * @param response
* HTTP response returned from the server for the request sent by * HTTP response returned from the server for the request sent by beginOpeningHandshake00().
* beginOpeningHandshake00().
* @throws WebSocketHandshakeException * @throws WebSocketHandshakeException
*/ */
@Override @Override

View File

@ -35,9 +35,8 @@ import org.jboss.netty.util.CharsetUtil;
/** /**
* <p> * <p>
* Performs client side opening and closing handshakes for <a * Performs client side opening and closing handshakes for <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a>.
* href="http://tools.ietf.org/html/rfc6455">RFC 6455</a>. This was originally * This was originally <a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
* <a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
* >draft-ietf-hybi-thewebsocketprotocol- 17</a> * >draft-ietf-hybi-thewebsocketprotocol- 17</a>
* </p> * </p>
*/ */
@ -54,21 +53,17 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
private boolean allowExtensions = false; private boolean allowExtensions = false;
/** /**
* Constructor specifying the destination web socket location and version to * Constructor specifying the destination web socket location and version to initiate
* initiate
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param version * @param version
* Version of web socket specification to use to connect to the * Version of web socket specification to use to connect to the server
* server
* @param subProtocol * @param subProtocol
* Sub protocol request sent to the server. * Sub protocol request sent to the server.
* @param allowExtensions * @param allowExtensions
* Allow extensions to be used in the reserved bits of the web * Allow extensions to be used in the reserved bits of the web socket frame
* socket frame
* @param customHeaders * @param customHeaders
* Map of custom headers to add to the client request * Map of custom headers to add to the client request
*/ */
@ -158,8 +153,7 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
* @param channel * @param channel
* Channel * Channel
* @param response * @param response
* HTTP response returned from the server for the request sent by * HTTP response returned from the server for the request sent by beginOpeningHandshake00().
* beginOpeningHandshake00().
* @throws WebSocketHandshakeException * @throws WebSocketHandshakeException
*/ */
@Override @Override

View File

@ -27,18 +27,14 @@ public class WebSocketClientHandshakerFactory {
* Instances a new handshaker * Instances a new handshaker
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param version * @param version
* Version of web socket specification to use to connect to the * Version of web socket specification to use to connect to the server
* server
* @param subProtocol * @param subProtocol
* Sub protocol request sent to the server. Null if no * Sub protocol request sent to the server. Null if no sub-protocol support is required.
* sub-protocol support is required.
* @param allowExtensions * @param allowExtensions
* Allow extensions to be used in the reserved bits of the web * Allow extensions to be used in the reserved bits of the web socket frame
* socket frame
* @param customHeaders * @param customHeaders
* custom HTTP headers * custom HTTP headers
* @throws WebSocketHandshakeException * @throws WebSocketHandshakeException

View File

@ -22,57 +22,57 @@ import org.jboss.netty.buffer.ChannelBuffer;
*/ */
public abstract class WebSocketFrame { public abstract class WebSocketFrame {
/** /**
* Flag to indicate if this frame is the final fragment in a message. The * Flag to indicate if this frame is the final fragment in a message. The first fragment (frame) may also be the
* first fragment (frame) may also be the final fragment. * final fragment.
*/ */
private boolean finalFragment = true; private boolean finalFragment = true;
/** /**
* RSV1, RSV2, RSV3 used for extensions * RSV1, RSV2, RSV3 used for extensions
*/ */
private int rsv = 0; private int rsv = 0;
/** /**
* Contents of this frame * Contents of this frame
*/ */
private ChannelBuffer binaryData; private ChannelBuffer binaryData;
/** /**
* Returns binary data * Returns binary data
*/ */
public ChannelBuffer getBinaryData() { public ChannelBuffer getBinaryData() {
return binaryData; return binaryData;
} }
/** /**
* Sets the binary data for this frame * Sets the binary data for this frame
*/ */
public void setBinaryData(ChannelBuffer binaryData) { public void setBinaryData(ChannelBuffer binaryData) {
this.binaryData = binaryData; this.binaryData = binaryData;
} }
/** /**
* Flag to indicate if this frame is the final fragment in a message. The * Flag to indicate if this frame is the final fragment in a message. The first fragment (frame) may also be the
* first fragment (frame) may also be the final fragment. * final fragment.
*/ */
public boolean isFinalFragment() { public boolean isFinalFragment() {
return finalFragment; return finalFragment;
} }
public void setFinalFragment(boolean finalFragment) { public void setFinalFragment(boolean finalFragment) {
this.finalFragment = finalFragment; this.finalFragment = finalFragment;
} }
/** /**
* Bits used for extensions to the standard. * Bits used for extensions to the standard.
*/ */
public int getRsv() { public int getRsv() {
return rsv; return rsv;
} }
public void setRsv(int rsv) { public void setRsv(int rsv) {
this.rsv = rsv; this.rsv = rsv;
} }
} }

View File

@ -19,5 +19,5 @@ package org.jboss.netty.handler.codec.http.websocketx;
* Type of web socket frames * Type of web socket frames
*/ */
public enum WebSocketFrameType { public enum WebSocketFrameType {
TEXT, BINARY, PING, PONG, CLOSE, CONTINUATION TEXT, BINARY, PING, PONG, CLOSE, CONTINUATION
} }

View File

@ -20,13 +20,13 @@ package org.jboss.netty.handler.codec.http.websocketx;
*/ */
public class WebSocketHandshakeException extends Exception { public class WebSocketHandshakeException extends Exception {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public WebSocketHandshakeException(String s) { public WebSocketHandshakeException(String s) {
super(s); super(s);
} }
public WebSocketHandshakeException(String s, Throwable throwable) { public WebSocketHandshakeException(String s, Throwable throwable) {
super(s, throwable); super(s, throwable);
} }
} }

View File

@ -27,159 +27,157 @@ import org.jboss.netty.util.CharsetUtil;
public abstract class WebSocketServerHandshaker { public abstract class WebSocketServerHandshaker {
private String webSocketURL; private String webSocketURL;
private String subProtocols; private String subProtocols;
private String[] subProtocolsArray = null; private String[] subProtocolsArray = null;
private WebSocketVersion version = WebSocketVersion.UNKNOWN; private WebSocketVersion version = WebSocketVersion.UNKNOWN;
/** /**
* Constructor specifying the destination web socket location * Constructor specifying the destination web socket location
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* "ws://myhost.com/mypath". Subsequent web socket frames will be * sent to this URL.
* sent to this URL. * @param subProtocols
* @param subProtocols * CSV of supported protocols. Null if sub protocols not supported.
* CSV of supported protocols. Null if sub protocols not */
* supported. public WebSocketServerHandshaker(String webSocketURL, String subProtocols) {
*/ this.webSocketURL = webSocketURL;
public WebSocketServerHandshaker(String webSocketURL, String subProtocols) { this.subProtocols = subProtocols;
this.webSocketURL = webSocketURL;
this.subProtocols = subProtocols;
if (this.subProtocols != null) { if (this.subProtocols != null) {
this.subProtocolsArray = subProtocols.split(","); this.subProtocolsArray = subProtocols.split(",");
for (int i = 0; i < this.subProtocolsArray.length; i++) { for (int i = 0; i < this.subProtocolsArray.length; i++) {
this.subProtocolsArray[i] = this.subProtocolsArray[i].trim(); this.subProtocolsArray[i] = this.subProtocolsArray[i].trim();
} }
} }
} }
/** /**
* Returns the URL of the web socket * Returns the URL of the web socket
*/ */
public String getWebSocketURL() { public String getWebSocketURL() {
return webSocketURL; return webSocketURL;
} }
public void setWebSocketURL(String webSocketURL) { public void setWebSocketURL(String webSocketURL) {
this.webSocketURL = webSocketURL; this.webSocketURL = webSocketURL;
} }
/** /**
* Returns the CSV of supported sub protocols * Returns the CSV of supported sub protocols
*/ */
public String getSubProtocols() { public String getSubProtocols() {
return subProtocols; return subProtocols;
} }
public void setSubProtocols(String subProtocols) { public void setSubProtocols(String subProtocols) {
this.subProtocols = subProtocols; this.subProtocols = subProtocols;
} }
/** /**
* Returns the version of the specification being supported * Returns the version of the specification being supported
*/ */
public WebSocketVersion getVersion() { public WebSocketVersion getVersion() {
return version; return version;
} }
public void setVersion(WebSocketVersion version) { public void setVersion(WebSocketVersion version) {
this.version = version; this.version = version;
} }
/** /**
* Performs the opening handshake * Performs the opening handshake
* *
* @param channel * @param channel
* Channel * Channel
* @param req * @param req
* HTTP Request * HTTP Request
* @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException
*/ */
public abstract void performOpeningHandshake(Channel channel, HttpRequest req); public abstract void performOpeningHandshake(Channel channel, HttpRequest req);
/** /**
* Performs the closing handshake * Performs the closing handshake
* *
* @param channel * @param channel
* Channel * Channel
* @param frame * @param frame
* Closing Frame that was received * Closing Frame that was received
*/ */
public abstract void performClosingHandshake(Channel channel, CloseWebSocketFrame frame); public abstract void performClosingHandshake(Channel channel, CloseWebSocketFrame frame);
/** /**
* Performs an MD5 hash * Performs an MD5 hash
* *
* @param bytes * @param bytes
* Data to hash * Data to hash
* @return Hashed data * @return Hashed data
*/ */
protected byte[] md5(byte[] bytes) { protected byte[] md5(byte[] bytes) {
try { try {
MessageDigest md = MessageDigest.getInstance("MD5"); MessageDigest md = MessageDigest.getInstance("MD5");
return md.digest(bytes); return md.digest(bytes);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
throw new InternalError("MD5 not supported on this platform"); throw new InternalError("MD5 not supported on this platform");
} }
} }
/** /**
* SHA-1 hashing. Instance this we think it is not thread safe * SHA-1 hashing. Instance this we think it is not thread safe
* *
* @param bytes * @param bytes
* byte to hash * byte to hash
* @return hashed * @return hashed
*/ */
protected byte[] sha1(byte[] bytes) { protected byte[] sha1(byte[] bytes) {
try { try {
MessageDigest md = MessageDigest.getInstance("SHA1"); MessageDigest md = MessageDigest.getInstance("SHA1");
return md.digest(bytes); return md.digest(bytes);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
throw new InternalError("SHA-1 not supported on this platform"); throw new InternalError("SHA-1 not supported on this platform");
} }
} }
/** /**
* Base 64 encoding * Base 64 encoding
* *
* @param bytes * @param bytes
* Bytes to encode * Bytes to encode
* @return encoded string * @return encoded string
*/ */
protected String base64Encode(byte[] bytes) { protected String base64Encode(byte[] bytes) {
ChannelBuffer hashed = ChannelBuffers.wrappedBuffer(bytes); ChannelBuffer hashed = ChannelBuffers.wrappedBuffer(bytes);
return Base64.encode(hashed).toString(CharsetUtil.UTF_8); return Base64.encode(hashed).toString(CharsetUtil.UTF_8);
} }
/** /**
* Selects the first matching supported sub protocol * Selects the first matching supported sub protocol
* *
* @param requestedSubProtocol * @param requestedSubProtocol
* CSV of protocols to be supported. e.g. "chat, superchat" * CSV of protocols to be supported. e.g. "chat, superchat"
* @return First matching supported sub protocol. Null if not found. * @return First matching supported sub protocol. Null if not found.
*/ */
protected String selectSubProtocol(String requestedSubProtocol) { protected String selectSubProtocol(String requestedSubProtocol) {
if (requestedSubProtocol == null || this.subProtocolsArray == null) { if (requestedSubProtocol == null || this.subProtocolsArray == null) {
return null; return null;
} }
String[] requesteSubProtocolsArray = requestedSubProtocol.split(","); String[] requesteSubProtocolsArray = requestedSubProtocol.split(",");
for (int i = 0; i < requesteSubProtocolsArray.length; i++) { for (int i = 0; i < requesteSubProtocolsArray.length; i++) {
String requesteSubProtocol = requesteSubProtocolsArray[i].trim(); String requesteSubProtocol = requesteSubProtocolsArray[i].trim();
for (String supportedSubProtocol : this.subProtocolsArray) { for (String supportedSubProtocol : this.subProtocolsArray) {
if (requesteSubProtocol.equals(supportedSubProtocol)) { if (requesteSubProtocol.equals(supportedSubProtocol)) {
return requesteSubProtocol; return requesteSubProtocol;
} }
} }
} }
// No match found // No match found
return null; return null;
} }
} }

View File

@ -48,10 +48,9 @@ import org.jboss.netty.logging.InternalLoggerFactory;
/** /**
* <p> * <p>
* Performs server side opening and closing handshakes for web socket * Performs server side opening and closing handshakes for web socket specification version <a
* specification version <a * href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00" >draft-ietf-hybi-thewebsocketprotocol-
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00" * 00</a>
* >draft-ietf-hybi-thewebsocketprotocol- 00</a>
* </p> * </p>
* <p> * <p>
* A very large portion of this code was taken from the Netty 3.2 HTTP example. * A very large portion of this code was taken from the Netty 3.2 HTTP example.
@ -59,143 +58,141 @@ import org.jboss.netty.logging.InternalLoggerFactory;
*/ */
public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker { public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker00.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker00.class);
/** /**
* Constructor specifying the destination web socket location * Constructor specifying the destination web socket location
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* "ws://myhost.com/mypath". Subsequent web socket frames will be * sent to this URL.
* sent to this URL. * @param subProtocols
* @param subProtocols * CSV of supported protocols
* CSV of supported protocols */
*/ public WebSocketServerHandshaker00(String webSocketURL, String subProtocols) {
public WebSocketServerHandshaker00(String webSocketURL, String subProtocols) { super(webSocketURL, subProtocols);
super(webSocketURL, subProtocols); }
}
/** /**
* <p> * <p>
* Handle the web socket handshake for the web socket specification <a href= * Handle the web socket handshake for the web socket specification <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">HyBi * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">HyBi version 0</a> and lower. This standard
* version 0</a> and lower. This standard is really a rehash of <a * is really a rehash of <a href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76" >hixie-76</a> and
* href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76" * <a href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75" >hixie-75</a>.
* >hixie-76</a> and <a * </p>
* href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75" *
* >hixie-75</a>. * <p>
* </p> * Browser request to the server:
* * </p>
* <p> *
* Browser request to the server: * <pre>
* </p> * GET /demo HTTP/1.1
* * Upgrade: WebSocket
* <pre> * Connection: Upgrade
* GET /demo HTTP/1.1 * Host: example.com
* Upgrade: WebSocket * Origin: http://example.com
* Connection: Upgrade * Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
* Host: example.com * Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
* Origin: http://example.com *
* Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 * ^n:ds[4U
* Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 * </pre>
* *
* ^n:ds[4U * <p>
* </pre> * Server response:
* * </p>
* <p> *
* Server response: * <pre>
* </p> * HTTP/1.1 101 WebSocket Protocol Handshake
* * Upgrade: WebSocket
* <pre> * Connection: Upgrade
* HTTP/1.1 101 WebSocket Protocol Handshake * Sec-WebSocket-Origin: http://example.com
* Upgrade: WebSocket * Sec-WebSocket-Location: ws://example.com/demo
* Connection: Upgrade * Sec-WebSocket-Protocol: sample
* Sec-WebSocket-Origin: http://example.com *
* Sec-WebSocket-Location: ws://example.com/demo * 8jKS'y:G*Co,Wxa-
* Sec-WebSocket-Protocol: sample * </pre>
* *
* 8jKS'y:G*Co,Wxa- * @param channel
* </pre> * Channel
* * @param req
* @param channel * HTTP request
* Channel * @throws NoSuchAlgorithmException
* @param req */
* HTTP request @Override
* @throws NoSuchAlgorithmException public void performOpeningHandshake(Channel channel, HttpRequest req) {
*/
@Override
public void performOpeningHandshake(Channel channel, HttpRequest req) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Channel %s web socket spec version 00 handshake", channel.getId())); logger.debug(String.format("Channel %s web socket spec version 00 handshake", channel.getId()));
} }
this.setVersion(WebSocketVersion.V00); this.setVersion(WebSocketVersion.V00);
// Serve the WebSocket handshake request. // Serve the WebSocket handshake request.
if (!Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION)) || !WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) { if (!Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION))
return; || !WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) {
} return;
}
// Hixie 75 does not contain these headers while Hixie 76 does // Hixie 75 does not contain these headers while Hixie 76 does
boolean isHixie76 = req.containsHeader(SEC_WEBSOCKET_KEY1) && req.containsHeader(SEC_WEBSOCKET_KEY2); boolean isHixie76 = req.containsHeader(SEC_WEBSOCKET_KEY1) && req.containsHeader(SEC_WEBSOCKET_KEY2);
// Create the WebSocket handshake response. // Create the WebSocket handshake response.
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake")); HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101,
res.addHeader(Names.UPGRADE, WEBSOCKET); isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake"));
res.addHeader(CONNECTION, Values.UPGRADE); res.addHeader(Names.UPGRADE, WEBSOCKET);
res.addHeader(CONNECTION, Values.UPGRADE);
// Fill in the headers and contents depending on handshake method. // Fill in the headers and contents depending on handshake method.
if (isHixie76) { if (isHixie76) {
// New handshake method with a challenge: // New handshake method with a challenge:
res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN)); res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
res.addHeader(SEC_WEBSOCKET_LOCATION, this.getWebSocketURL()); res.addHeader(SEC_WEBSOCKET_LOCATION, this.getWebSocketURL());
String protocol = req.getHeader(SEC_WEBSOCKET_PROTOCOL); String protocol = req.getHeader(SEC_WEBSOCKET_PROTOCOL);
if (protocol != null) { if (protocol != null) {
res.addHeader(SEC_WEBSOCKET_PROTOCOL, selectSubProtocol(protocol)); res.addHeader(SEC_WEBSOCKET_PROTOCOL, selectSubProtocol(protocol));
} }
// Calculate the answer of the challenge. // Calculate the answer of the challenge.
String key1 = req.getHeader(SEC_WEBSOCKET_KEY1); String key1 = req.getHeader(SEC_WEBSOCKET_KEY1);
String key2 = req.getHeader(SEC_WEBSOCKET_KEY2); String key2 = req.getHeader(SEC_WEBSOCKET_KEY2);
int a = (int) (Long.parseLong(key1.replaceAll("[^0-9]", "")) / key1.replaceAll("[^ ]", "").length()); int a = (int) (Long.parseLong(key1.replaceAll("[^0-9]", "")) / key1.replaceAll("[^ ]", "").length());
int b = (int) (Long.parseLong(key2.replaceAll("[^0-9]", "")) / key2.replaceAll("[^ ]", "").length()); int b = (int) (Long.parseLong(key2.replaceAll("[^0-9]", "")) / key2.replaceAll("[^ ]", "").length());
long c = req.getContent().readLong(); long c = req.getContent().readLong();
ChannelBuffer input = ChannelBuffers.buffer(16); ChannelBuffer input = ChannelBuffers.buffer(16);
input.writeInt(a); input.writeInt(a);
input.writeInt(b); input.writeInt(b);
input.writeLong(c); input.writeLong(c);
ChannelBuffer output = ChannelBuffers.wrappedBuffer(this.md5(input.array())); ChannelBuffer output = ChannelBuffers.wrappedBuffer(this.md5(input.array()));
res.setContent(output); res.setContent(output);
} else { } else {
// Old Hixie 75 handshake method with no challenge: // Old Hixie 75 handshake method with no challenge:
res.addHeader(WEBSOCKET_ORIGIN, req.getHeader(ORIGIN)); res.addHeader(WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
res.addHeader(WEBSOCKET_LOCATION, this.getWebSocketURL()); res.addHeader(WEBSOCKET_LOCATION, this.getWebSocketURL());
String protocol = req.getHeader(WEBSOCKET_PROTOCOL); String protocol = req.getHeader(WEBSOCKET_PROTOCOL);
if (protocol != null) { if (protocol != null) {
res.addHeader(WEBSOCKET_PROTOCOL, selectSubProtocol(protocol)); res.addHeader(WEBSOCKET_PROTOCOL, selectSubProtocol(protocol));
} }
} }
// Upgrade the connection and send the handshake response. // Upgrade the connection and send the handshake response.
ChannelPipeline p = channel.getPipeline(); ChannelPipeline p = channel.getPipeline();
p.remove(HttpChunkAggregator.class); p.remove(HttpChunkAggregator.class);
p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket00FrameDecoder()); p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket00FrameDecoder());
channel.write(res); channel.write(res);
p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket00FrameEncoder()); p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket00FrameEncoder());
} }
/** /**
* Echo back the closing frame * Echo back the closing frame
* *
* @param channel * @param channel
* Channel * Channel
* @param frame * @param frame
* Web Socket frame that was received * Web Socket frame that was received
*/ */
@Override @Override
public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) { public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) {
channel.write(frame); channel.write(frame);
} }
} }

View File

@ -38,16 +38,14 @@ import org.jboss.netty.util.CharsetUtil;
/** /**
* <p> * <p>
* Performs server side opening and closing handshakes for web socket * Performs server side opening and closing handshakes for web socket specification version <a
* specification version <a * href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10" >draft-ietf-hybi-thewebsocketprotocol-
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10" * 10</a>
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
* </p> * </p>
*/ */
public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker { public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
private static final InternalLogger logger = InternalLoggerFactory private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker08.class);
.getInstance(WebSocketServerHandshaker08.class);
public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
@ -57,17 +55,14 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
* Constructor specifying the destination web socket location * Constructor specifying the destination web socket location
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param subProtocols * @param subProtocols
* CSV of supported protocols * CSV of supported protocols
* @param allowExtensions * @param allowExtensions
* Allow extensions to be used in the reserved bits of the web * Allow extensions to be used in the reserved bits of the web socket frame
* socket frame
*/ */
public WebSocketServerHandshaker08(String webSocketURL, public WebSocketServerHandshaker08(String webSocketURL, String subProtocols, boolean allowExtensions) {
String subProtocols, boolean allowExtensions) {
super(webSocketURL, subProtocols); super(webSocketURL, subProtocols);
this.allowExtensions = allowExtensions; this.allowExtensions = allowExtensions;
} }
@ -75,8 +70,8 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
/** /**
* <p> * <p>
* Handle the web socket handshake for the web socket specification <a href= * Handle the web socket handshake for the web socket specification <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-08">HyBi * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-08">HyBi version 8 to 10</a>. Version 8, 9 and
* version 8 to 10</a>. Version 8, 9 and 10 share the same wire protocol. * 10 share the same wire protocol.
* </p> * </p>
* *
* <p> * <p>
@ -116,13 +111,10 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
public void performOpeningHandshake(Channel channel, HttpRequest req) { public void performOpeningHandshake(Channel channel, HttpRequest req) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format("Channel %s WS version 08 handshake", channel.getId()));
"Channel %s WS version 08 handshake",
channel.getId()));
} }
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, "Switching Protocols"));
new HttpResponseStatus(101, "Switching Protocols"));
this.setVersion(WebSocketVersion.V08); this.setVersion(WebSocketVersion.V08);
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY); String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
@ -135,9 +127,7 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
String accept = base64Encode(sha1); String accept = base64Encode(sha1);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format("WS Version 08 Server Handshake key: %s. Response: %s.", key, accept));
"WS Version 08 Server Handshake key: %s. Response: %s.",
key, accept));
} }
res.setStatus(new HttpResponseStatus(101, "Switching Protocols")); res.setStatus(new HttpResponseStatus(101, "Switching Protocols"));
@ -146,8 +136,7 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept); res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept);
String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
if (protocol != null) { if (protocol != null) {
res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.selectSubProtocol(protocol));
this.selectSubProtocol(protocol));
} }
channel.write(res); channel.write(res);
@ -155,10 +144,8 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
// Upgrade the connection and send the handshake response. // Upgrade the connection and send the handshake response.
ChannelPipeline p = channel.getPipeline(); ChannelPipeline p = channel.getPipeline();
p.remove(HttpChunkAggregator.class); p.remove(HttpChunkAggregator.class);
p.replace(HttpRequestDecoder.class, "wsdecoder", p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket08FrameDecoder(true, this.allowExtensions));
new WebSocket08FrameDecoder(true, this.allowExtensions)); p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket08FrameEncoder(false));
p.replace(HttpResponseEncoder.class, "wsencoder",
new WebSocket08FrameEncoder(false));
} }
@ -171,8 +158,7 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
* Web Socket frame that was received * Web Socket frame that was received
*/ */
@Override @Override
public void performClosingHandshake(Channel channel, public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) {
CloseWebSocketFrame frame) {
ChannelFuture f = channel.write(frame); ChannelFuture f = channel.write(frame);
f.addListener(ChannelFutureListener.CLOSE); f.addListener(ChannelFutureListener.CLOSE);
} }

View File

@ -38,16 +38,14 @@ import org.jboss.netty.util.CharsetUtil;
/** /**
* <p> * <p>
* Performs server side opening and closing handshakes for <a * Performs server side opening and closing handshakes for <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a>.
* href="http://tools.ietf.org/html/rfc6455">RFC 6455</a>. This was originally * This was originally <a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
* <a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
* >draft-ietf-hybi-thewebsocketprotocol- 17</a> * >draft-ietf-hybi-thewebsocketprotocol- 17</a>
* </p> * </p>
*/ */
public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker { public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
private static final InternalLogger logger = InternalLoggerFactory private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker13.class);
.getInstance(WebSocketServerHandshaker13.class);
public static final String WEBSOCKET_17_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; public static final String WEBSOCKET_17_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
@ -57,17 +55,14 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
* Constructor specifying the destination web socket location * Constructor specifying the destination web socket location
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param subProtocols * @param subProtocols
* CSV of supported protocols * CSV of supported protocols
* @param allowExtensions * @param allowExtensions
* Allow extensions to be used in the reserved bits of the web * Allow extensions to be used in the reserved bits of the web socket frame
* socket frame
*/ */
public WebSocketServerHandshaker13(String webSocketURL, public WebSocketServerHandshaker13(String webSocketURL, String subProtocols, boolean allowExtensions) {
String subProtocols, boolean allowExtensions) {
super(webSocketURL, subProtocols); super(webSocketURL, subProtocols);
this.allowExtensions = allowExtensions; this.allowExtensions = allowExtensions;
} }
@ -75,8 +70,8 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
/** /**
* <p> * <p>
* Handle the web socket handshake for the web socket specification <a href= * Handle the web socket handshake for the web socket specification <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi versions 13-17</a>. Versions 13-17
* versions 13-17</a>. Versions 13-17 share the same wire protocol. * share the same wire protocol.
* </p> * </p>
* *
* <p> * <p>
@ -116,13 +111,10 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
public void performOpeningHandshake(Channel channel, HttpRequest req) { public void performOpeningHandshake(Channel channel, HttpRequest req) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format("Channel %s WS version 13 handshake", channel.getId()));
"Channel %s WS version 13 handshake",
channel.getId()));
} }
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, "Switching Protocols"));
new HttpResponseStatus(101, "Switching Protocols"));
this.setVersion(WebSocketVersion.V13); this.setVersion(WebSocketVersion.V13);
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY); String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
@ -135,9 +127,7 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
String accept = base64Encode(sha1); String accept = base64Encode(sha1);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format("WS Version 13 Server Handshake key: %s. Response: %s.", key, accept));
"WS Version 13 Server Handshake key: %s. Response: %s.", key,
accept));
} }
res.setStatus(new HttpResponseStatus(101, "Switching Protocols")); res.setStatus(new HttpResponseStatus(101, "Switching Protocols"));
@ -146,8 +136,7 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept); res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept);
String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
if (protocol != null) { if (protocol != null) {
res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.selectSubProtocol(protocol));
this.selectSubProtocol(protocol));
} }
channel.write(res); channel.write(res);
@ -155,10 +144,8 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
// Upgrade the connection and send the handshake response. // Upgrade the connection and send the handshake response.
ChannelPipeline p = channel.getPipeline(); ChannelPipeline p = channel.getPipeline();
p.remove(HttpChunkAggregator.class); p.remove(HttpChunkAggregator.class);
p.replace(HttpRequestDecoder.class, "wsdecoder", p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket13FrameDecoder(true, this.allowExtensions));
new WebSocket13FrameDecoder(true, this.allowExtensions)); p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false));
p.replace(HttpResponseEncoder.class, "wsencoder",
new WebSocket13FrameEncoder(false));
} }
@ -171,8 +158,7 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
* Web Socket frame that was received * Web Socket frame that was received
*/ */
@Override @Override
public void performClosingHandshake(Channel channel, public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) {
CloseWebSocketFrame frame) {
ChannelFuture f = channel.write(frame); ChannelFuture f = channel.write(frame);
f.addListener(ChannelFutureListener.CLOSE); f.addListener(ChannelFutureListener.CLOSE);
} }

View File

@ -36,17 +36,14 @@ public class WebSocketServerHandshakerFactory {
/** /**
* Constructor specifying the destination web socket location * Constructor specifying the destination web socket location
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param subProtocols * @param subProtocols
* CSV of supported protocols. Null if sub protocols not * CSV of supported protocols. Null if sub protocols not supported.
* supported.
* @param allowExtensions * @param allowExtensions
* Allow extensions to be used in the reserved bits of the web * Allow extensions to be used in the reserved bits of the web socket frame
* socket frame
*/ */
public WebSocketServerHandshakerFactory(String webSocketURL, String subProtocols, boolean allowExtensions) { public WebSocketServerHandshakerFactory(String webSocketURL, String subProtocols, boolean allowExtensions) {
this.webSocketURL = webSocketURL; this.webSocketURL = webSocketURL;
@ -56,9 +53,9 @@ public class WebSocketServerHandshakerFactory {
/** /**
* Instances a new handshaker * Instances a new handshaker
* *
* @return A new WebSocketServerHandshaker for the requested web socket * @return A new WebSocketServerHandshaker for the requested web socket version. Null if web socket version is not
* version. Null if web socket version is not supported. * supported.
*/ */
public WebSocketServerHandshaker newHandshaker(HttpRequest req) { public WebSocketServerHandshaker newHandshaker(HttpRequest req) {
@ -83,7 +80,7 @@ public class WebSocketServerHandshakerFactory {
/** /**
* Return that we need cannot not support the web socket version * Return that we need cannot not support the web socket version
* *
* @param channel * @param channel
* Channel * Channel
*/ */

View File

@ -24,30 +24,27 @@ public enum WebSocketVersion {
UNKNOWN, UNKNOWN,
/** /**
* <a href= * <a href= "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00"
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00"
* >draft-ietf-hybi-thewebsocketprotocol- 00</a>. * >draft-ietf-hybi-thewebsocketprotocol- 00</a>.
*/ */
V00, V00,
/** /**
* <a href= * <a href= "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
* >draft-ietf-hybi-thewebsocketprotocol- 10</a> * >draft-ietf-hybi-thewebsocketprotocol- 10</a>
*/ */
V08, V08,
/** /**
* <a href="http://tools.ietf.org/html/rfc6455 ">RFC 6455</a>. This was * <a href="http://tools.ietf.org/html/rfc6455 ">RFC 6455</a>. This was originally <a href=
* originally <a href= * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17" >draft-ietf-hybi-thewebsocketprotocol-
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17" * 17</a>
* >draft-ietf-hybi-thewebsocketprotocol- 17</a>
*/ */
V13; V13;
public String toHttpHeaderValue() { public String toHttpHeaderValue() {
if (this == V00) { if (this == V00) {
return "0"; return "0";
} else if (this == V08) { } else if (this == V08) {
return "8"; return "8";
} else if (this == V13) { } else if (this == V13) {