Change tabs to spaces.

This commit is contained in:
vibul 2011-12-15 22:36:47 +11:00
parent d8d3cfd60b
commit cc494310da
48 changed files with 2866 additions and 2870 deletions

View File

@ -29,24 +29,24 @@ import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
* suite * suite
*/ */
public class WebSocketServer { public class WebSocketServer {
public static void main(String[] args) { public static void main(String[] args) {
ConsoleHandler ch = new ConsoleHandler(); ConsoleHandler ch = new ConsoleHandler();
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

@ -48,86 +48,86 @@ import org.jboss.netty.util.CharsetUtil;
* Handles handshakes and messages * Handles handshakes and messages
*/ */
public class WebSocketServerHandler extends SimpleChannelUpstreamHandler { public class WebSocketServerHandler extends SimpleChannelUpstreamHandler {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandler.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandler.class);
private WebSocketServerHandshaker handshaker = null; private WebSocketServerHandshaker handshaker = null;
@Override @Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
Object msg = e.getMessage(); Object msg = e.getMessage();
if (msg instanceof HttpRequest) { if (msg instanceof HttpRequest) {
handleHttpRequest(ctx, (HttpRequest) msg); handleHttpRequest(ctx, (HttpRequest) msg);
} else if (msg instanceof WebSocketFrame) { } else if (msg instanceof WebSocketFrame) {
handleWebSocketFrame(ctx, (WebSocketFrame) msg); handleWebSocketFrame(ctx, (WebSocketFrame) msg);
} }
} }
private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception { private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
// Allow only GET methods. // Allow only GET methods.
if (req.getMethod() != GET) { if (req.getMethod() != GET) {
sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN)); sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN));
return; return;
} }
// Handshake // Handshake
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory( WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
this.getWebSocketLocation(req), null, false); this.getWebSocketLocation(req), null, false);
this.handshaker = wsFactory.newHandshaker(req); this.handshaker = wsFactory.newHandshaker(req);
if (this.handshaker == null) { if (this.handshaker == null) {
wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel()); wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel());
} else { } else {
this.handshaker.performOpeningHandshake(ctx.getChannel(), req); this.handshaker.performOpeningHandshake(ctx.getChannel(), req);
} }
} }
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
logger.debug(String logger.debug(String
.format("Channel %s received %s", ctx.getChannel().getId(), frame.getClass().getSimpleName())); .format("Channel %s received %s", ctx.getChannel().getId(), frame.getClass().getSimpleName()));
if (frame instanceof CloseWebSocketFrame) { if (frame instanceof CloseWebSocketFrame) {
this.handshaker.performClosingHandshake(ctx.getChannel(), (CloseWebSocketFrame) frame); this.handshaker.performClosingHandshake(ctx.getChannel(), (CloseWebSocketFrame) frame);
} else if (frame instanceof PingWebSocketFrame) { } else if (frame instanceof PingWebSocketFrame) {
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( ctx.getChannel().write(
new TextWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); 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()));
} else if (frame instanceof ContinuationWebSocketFrame) { } else if (frame instanceof ContinuationWebSocketFrame) {
ctx.getChannel().write( ctx.getChannel().write(
new ContinuationWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); new ContinuationWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
} else if (frame instanceof PongWebSocketFrame) { } else if (frame instanceof PongWebSocketFrame) {
// Ignore // Ignore
} else { } else {
throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass() throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
.getName())); .getName()));
} }
} }
private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) { private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
// Generate an error page if response status code is not OK (200). // Generate an error page if response status code is not OK (200).
if (res.getStatus().getCode() != 200) { if (res.getStatus().getCode() != 200) {
res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8)); res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8));
setContentLength(res, res.getContent().readableBytes()); setContentLength(res, res.getContent().readableBytes());
} }
// Send the response and close the connection if necessary. // Send the response and close the connection if necessary.
ChannelFuture f = ctx.getChannel().write(res); ChannelFuture f = ctx.getChannel().write(res);
if (!isKeepAlive(req) || res.getStatus().getCode() != 200) { if (!isKeepAlive(req) || res.getStatus().getCode() != 200) {
f.addListener(ChannelFutureListener.CLOSE); f.addListener(ChannelFutureListener.CLOSE);
} }
} }
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
e.getCause().printStackTrace(); e.getCause().printStackTrace();
e.getChannel().close(); e.getChannel().close();
} }
private String getWebSocketLocation(HttpRequest req) { private String getWebSocketLocation(HttpRequest req) {
return "ws://" + req.getHeader(HttpHeaders.Names.HOST); return "ws://" + req.getHeader(HttpHeaders.Names.HOST);
} }
} }

View File

@ -26,13 +26,13 @@ import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
/** /**
*/ */
public class WebSocketServerPipelineFactory implements ChannelPipelineFactory { public class WebSocketServerPipelineFactory 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();
pipeline.addLast("decoder", new HttpRequestDecoder()); pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpChunkAggregator(65536)); pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
pipeline.addLast("encoder", new HttpResponseEncoder()); pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("handler", new WebSocketServerHandler()); pipeline.addLast("handler", new WebSocketServerHandler());
return pipeline; return pipeline;
} }
} }

View File

@ -47,11 +47,13 @@
* } * }
* </code> * </code>
* *
* <p>10. Run the test <tt>python fuzzing_client.py</tt>. Note that the actual test case python code is * <p>11. Run <tt>WebSocketServer</tt> in this package.
*
* <p>11. Run the test <tt>python fuzzing_client.py</tt>. Note that the actual test case python code is
* located in <tt>/usr/local/lib/python2.6/dist-packages/autobahn-0.4.3-py2.6.egg/autobahn/cases</tt> * located in <tt>/usr/local/lib/python2.6/dist-packages/autobahn-0.4.3-py2.6.egg/autobahn/cases</tt>
* and not in the checked out git repository. * and not in the checked out git repository.
* *
* <p>11. See the results in <tt>reports/servers/index.html</tt> * <p>12. See the results in <tt>reports/servers/index.html</tt>
*/ */
package org.jboss.netty.example.http.websocketx.autobahn; package org.jboss.netty.example.http.websocketx.autobahn;

View File

@ -36,97 +36,97 @@ import org.jboss.netty.handler.codec.http.websocketx.WebSocketVersion;
*/ */
public class App { public class App {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
ConsoleHandler ch = new ConsoleHandler(); ConsoleHandler ch = new ConsoleHandler();
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);
runClient(); runClient();
System.exit(0); System.exit(0);
} }
/** /**
* Send and receive some messages using a web socket client * Send and receive some messages using a web socket client
* *
* @throws Exception * @throws Exception
*/ */
public static void runClient() throws Exception { public static void runClient() throws Exception {
MyCallbackHandler callbackHandler = new MyCallbackHandler(); MyCallbackHandler callbackHandler = new MyCallbackHandler();
WebSocketClientFactory factory = new WebSocketClientFactory(); WebSocketClientFactory factory = new WebSocketClientFactory();
HashMap<String, String> customHeaders = new HashMap<String, String>(); HashMap<String, String> customHeaders = new HashMap<String, String>();
customHeaders.put("MyHeader", "MyValue"); customHeaders.put("MyHeader", "MyValue");
// Connect with V13 (RFC 6455). You can change it to V08 or V00. // Connect with V13 (RFC 6455). You can change it to V08 or V00.
// If you change it to V00, ping is not supported and remember to change // If you change it to V00, ping is not supported and remember to change
// HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline. // HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
WebSocketClient client = factory.newClient(new URI("ws://localhost:8080/websocket"), WebSocketVersion.V13, WebSocketClient client = factory.newClient(new URI("ws://localhost:8080/websocket"), WebSocketVersion.V13,
callbackHandler, customHeaders); callbackHandler, customHeaders);
// Connect // Connect
System.out.println("WebSocket Client connecting"); System.out.println("WebSocket Client connecting");
client.connect().awaitUninterruptibly(); client.connect().awaitUninterruptibly();
Thread.sleep(200); Thread.sleep(200);
// Send 10 messages and wait for responses // Send 10 messages and wait for responses
System.out.println("WebSocket Client sending message"); System.out.println("WebSocket Client sending message");
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
client.send(new TextWebSocketFrame("Message #" + i)); client.send(new TextWebSocketFrame("Message #" + i));
} }
Thread.sleep(1000); Thread.sleep(1000);
// Ping // Ping
System.out.println("WebSocket Client sending ping"); System.out.println("WebSocket Client sending ping");
client.send(new PingWebSocketFrame(ChannelBuffers.copiedBuffer(new byte[] { 1, 2, 3, 4, 5, 6 }))); client.send(new PingWebSocketFrame(ChannelBuffers.copiedBuffer(new byte[] { 1, 2, 3, 4, 5, 6 })));
Thread.sleep(1000); Thread.sleep(1000);
// Close // Close
System.out.println("WebSocket Client sending close"); System.out.println("WebSocket Client sending close");
client.send(new CloseWebSocketFrame()); client.send(new CloseWebSocketFrame());
Thread.sleep(1000); Thread.sleep(1000);
// Disconnect // Disconnect
client.disconnect(); client.disconnect();
} }
/** /**
* Our web socket callback handler for this app * Our web socket callback handler for this app
*/ */
public static class MyCallbackHandler implements WebSocketCallback { public static class MyCallbackHandler implements WebSocketCallback {
public boolean connected = false; public boolean connected = false;
public ArrayList<String> messagesReceived = new ArrayList<String>(); public ArrayList<String> messagesReceived = new ArrayList<String>();
public MyCallbackHandler() { public MyCallbackHandler() {
} }
public void onConnect(WebSocketClient client) { public void onConnect(WebSocketClient client) {
System.out.println("WebSocket Client connected!"); System.out.println("WebSocket Client connected!");
connected = true; connected = true;
} }
public void onDisconnect(WebSocketClient client) { public void onDisconnect(WebSocketClient client) {
System.out.println("WebSocket Client disconnected!"); System.out.println("WebSocket Client disconnected!");
connected = false; connected = false;
} }
public void onMessage(WebSocketClient client, WebSocketFrame frame) { public void onMessage(WebSocketClient client, WebSocketFrame frame) {
if (frame instanceof TextWebSocketFrame) { if (frame instanceof TextWebSocketFrame) {
TextWebSocketFrame textFrame = (TextWebSocketFrame) frame; TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
System.out.println("WebSocket Client received message:" + textFrame.getText()); System.out.println("WebSocket Client received message:" + textFrame.getText());
messagesReceived.add(textFrame.getText()); messagesReceived.add(textFrame.getText());
} else if (frame instanceof PongWebSocketFrame) { } else if (frame instanceof PongWebSocketFrame) {
System.out.println("WebSocket Client received pong"); System.out.println("WebSocket Client received pong");
} else if (frame instanceof CloseWebSocketFrame) { } else if (frame instanceof CloseWebSocketFrame) {
System.out.println("WebSocket Client received closing"); System.out.println("WebSocket Client received closing");
} }
} }
public void onError(Throwable t) { public void onError(Throwable t) {
System.out.println("WebSocket Client error " + t.toString()); System.out.println("WebSocket Client error " + t.toString());
} }
} }
} }

View File

@ -31,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

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

View File

@ -43,50 +43,50 @@ import java.util.concurrent.Executors;
*/ */
public class WebSocketClientFactory { public class WebSocketClientFactory {
private final NioClientSocketChannelFactory socketChannelFactory = new NioClientSocketChannelFactory( private final NioClientSocketChannelFactory socketChannelFactory = new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool()); Executors.newCachedThreadPool(), Executors.newCachedThreadPool());
/** /**
* Create a new WebSocket client * Create a new WebSocket client
* *
* @param url * @param url
* URL to connect to. * URL to connect to.
* @param version * @param version
* Web Socket version to support * Web Socket version to support
* @param callback * @param callback
* 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 connect. * @return A WebSocket client. Call {@link WebSocketClient#connect()} to 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) {
ClientBootstrap bootstrap = new ClientBootstrap(socketChannelFactory); ClientBootstrap bootstrap = new ClientBootstrap(socketChannelFactory);
String protocol = url.getScheme(); String protocol = url.getScheme();
if (!protocol.equals("ws") && !protocol.equals("wss")) { if (!protocol.equals("ws") && !protocol.equals("wss")) {
throw new IllegalArgumentException("Unsupported protocol: " + protocol); throw new IllegalArgumentException("Unsupported protocol: " + protocol);
} }
final WebSocketClientHandler clientHandler = new WebSocketClientHandler(bootstrap, url, version, callback, final WebSocketClientHandler clientHandler = new WebSocketClientHandler(bootstrap, url, version, callback,
customHeaders); customHeaders);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() { bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception { public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline(); ChannelPipeline pipeline = Channels.pipeline();
// If you wish to support HyBi V00, you need to use // If you wish to support HyBi V00, you need to use
// WebSocketHttpResponseDecoder instead for // WebSocketHttpResponseDecoder instead for
// HttpResponseDecoder. // HttpResponseDecoder.
pipeline.addLast("decoder", new HttpResponseDecoder()); pipeline.addLast("decoder", new HttpResponseDecoder());
pipeline.addLast("encoder", new HttpRequestEncoder()); pipeline.addLast("encoder", new HttpRequestEncoder());
pipeline.addLast("ws-handler", clientHandler); pipeline.addLast("ws-handler", clientHandler);
return pipeline; return pipeline;
} }
}); });
return clientHandler; return clientHandler;
} }
} }

View File

@ -49,79 +49,79 @@ import org.jboss.netty.util.CharsetUtil;
*/ */
public class WebSocketClientHandler extends SimpleChannelUpstreamHandler implements WebSocketClient { public class WebSocketClientHandler extends SimpleChannelUpstreamHandler implements WebSocketClient {
private final ClientBootstrap bootstrap; private final ClientBootstrap bootstrap;
private URI url; private URI url;
private final WebSocketCallback callback; private final WebSocketCallback callback;
private Channel channel; private Channel channel;
private WebSocketClientHandshaker handshaker = null; private WebSocketClientHandshaker handshaker = null;
private final WebSocketVersion version; private final WebSocketVersion version;
private Map<String, String> customHeaders = null; private Map<String, String> customHeaders = null;
public WebSocketClientHandler(ClientBootstrap bootstrap, URI url, WebSocketVersion version, public WebSocketClientHandler(ClientBootstrap bootstrap, URI url, WebSocketVersion version,
WebSocketCallback callback, Map<String, String> customHeaders) { WebSocketCallback callback, Map<String, String> customHeaders) {
this.bootstrap = bootstrap; this.bootstrap = bootstrap;
this.url = url; this.url = url;
this.version = version; this.version = version;
this.callback = callback; this.callback = callback;
this.customHeaders = customHeaders; this.customHeaders = customHeaders;
} }
@Override @Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
channel = e.getChannel(); channel = e.getChannel();
this.handshaker = new WebSocketClientHandshakerFactory() this.handshaker = new WebSocketClientHandshakerFactory()
.newHandshaker(url, version, null, false, customHeaders); .newHandshaker(url, version, null, false, customHeaders);
handshaker.performOpeningHandshake(channel); handshaker.performOpeningHandshake(channel);
} }
@Override @Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
callback.onDisconnect(this); callback.onDisconnect(this);
} }
@Override @Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
if (!handshaker.isOpeningHandshakeCompleted()) { if (!handshaker.isOpeningHandshakeCompleted()) {
handshaker.performClosingHandshake(ctx.getChannel(), (HttpResponse) e.getMessage()); handshaker.performClosingHandshake(ctx.getChannel(), (HttpResponse) e.getMessage());
callback.onConnect(this); callback.onConnect(this);
return; return;
} }
if (e.getMessage() instanceof HttpResponse) { if (e.getMessage() instanceof HttpResponse) {
HttpResponse response = (HttpResponse) e.getMessage(); HttpResponse response = (HttpResponse) e.getMessage();
throw new WebSocketException("Unexpected HttpResponse (status=" + response.getStatus() + ", content=" throw new WebSocketException("Unexpected HttpResponse (status=" + response.getStatus() + ", content="
+ response.getContent().toString(CharsetUtil.UTF_8) + ")"); + response.getContent().toString(CharsetUtil.UTF_8) + ")");
} }
WebSocketFrame frame = (WebSocketFrame) e.getMessage(); WebSocketFrame frame = (WebSocketFrame) e.getMessage();
callback.onMessage(this, frame); callback.onMessage(this, frame);
} }
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
final Throwable t = e.getCause(); final Throwable t = e.getCause();
callback.onError(t); callback.onError(t);
e.getChannel().close(); e.getChannel().close();
} }
public ChannelFuture connect() { public ChannelFuture connect() {
return bootstrap.connect(new InetSocketAddress(url.getHost(), url.getPort())); return bootstrap.connect(new InetSocketAddress(url.getHost(), url.getPort()));
} }
public ChannelFuture disconnect() { public ChannelFuture disconnect() {
return channel.close(); return channel.close();
} }
public ChannelFuture send(WebSocketFrame frame) { public ChannelFuture send(WebSocketFrame frame) {
return channel.write(frame); return channel.write(frame);
} }
public URI getUrl() { public URI getUrl() {
return url; return url;
} }
public void setUrl(URI url) { public void setUrl(URI url) {
this.url = url; this.url = url;
} }
} }

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

@ -36,33 +36,30 @@ import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
* *
* <ul> * <ul>
* <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00) * <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00)
* <li>
* <li>Chrome 6-13 (draft-ietf-hybi-thewebsocketprotocol-00) * <li>Chrome 6-13 (draft-ietf-hybi-thewebsocketprotocol-00)
* <li>
* <li>Chrome 14+ (draft-ietf-hybi-thewebsocketprotocol-10) * <li>Chrome 14+ (draft-ietf-hybi-thewebsocketprotocol-10)
* <li> * <li>Chrome 16+ (RFC 6455 aka draft-ietf-hybi-thewebsocketprotocol-17)
* <li>Firefox 7+ (draft-ietf-hybi-thewebsocketprotocol-10) * <li>Firefox 7+ (draft-ietf-hybi-thewebsocketprotocol-10)
* <li>
* </ul> * </ul>
*/ */
public class WebSocketServer { public class WebSocketServer {
public static void main(String[] args) { public static void main(String[] args) {
ConsoleHandler ch = new ConsoleHandler(); ConsoleHandler ch = new ConsoleHandler();
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()));
// 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(8080)); bootstrap.bind(new InetSocketAddress(8080));
System.out System.out
.println("Web Socket Server started on 8080. Open your browser and navigate to http://localhost:8080/"); .println("Web Socket Server started on 8080. Open your browser and navigate to http://localhost:8080/");
} }
} }

View File

@ -48,99 +48,99 @@ import org.jboss.netty.util.CharsetUtil;
* Handles handshakes and messages * Handles handshakes and messages
*/ */
public class WebSocketServerHandler extends SimpleChannelUpstreamHandler { public class WebSocketServerHandler extends SimpleChannelUpstreamHandler {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandler.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandler.class);
private static final String WEBSOCKET_PATH = "/websocket"; private static final String WEBSOCKET_PATH = "/websocket";
private WebSocketServerHandshaker handshaker = null; private WebSocketServerHandshaker handshaker = null;
@Override @Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
Object msg = e.getMessage(); Object msg = e.getMessage();
if (msg instanceof HttpRequest) { if (msg instanceof HttpRequest) {
handleHttpRequest(ctx, (HttpRequest) msg); handleHttpRequest(ctx, (HttpRequest) msg);
} else if (msg instanceof WebSocketFrame) { } else if (msg instanceof WebSocketFrame) {
handleWebSocketFrame(ctx, (WebSocketFrame) msg); handleWebSocketFrame(ctx, (WebSocketFrame) msg);
} }
} }
private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception { private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
// Allow only GET methods. // Allow only GET methods.
if (req.getMethod() != GET) { if (req.getMethod() != GET) {
sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN)); sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN));
return; return;
} }
// Send the demo page and favicon.ico // Send the demo page and favicon.ico
if (req.getUri().equals("/")) { if (req.getUri().equals("/")) {
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, OK); HttpResponse res = new DefaultHttpResponse(HTTP_1_1, OK);
ChannelBuffer content = WebSocketServerIndexPage.getContent(getWebSocketLocation(req)); ChannelBuffer content = WebSocketServerIndexPage.getContent(getWebSocketLocation(req));
res.setHeader(CONTENT_TYPE, "text/html; charset=UTF-8"); res.setHeader(CONTENT_TYPE, "text/html; charset=UTF-8");
setContentLength(res, content.readableBytes()); setContentLength(res, content.readableBytes());
res.setContent(content); res.setContent(content);
sendHttpResponse(ctx, req, res); sendHttpResponse(ctx, req, res);
return; return;
} else if (req.getUri().equals("/favicon.ico")) { } else if (req.getUri().equals("/favicon.ico")) {
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND); HttpResponse res = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND);
sendHttpResponse(ctx, req, res); sendHttpResponse(ctx, req, res);
return; return;
} }
// Handshake // Handshake
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory( WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
this.getWebSocketLocation(req), null, false); this.getWebSocketLocation(req), null, false);
this.handshaker = wsFactory.newHandshaker(req); this.handshaker = wsFactory.newHandshaker(req);
if (this.handshaker == null) { if (this.handshaker == null) {
wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel()); wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel());
} else { } else {
this.handshaker.performOpeningHandshake(ctx.getChannel(), req); this.handshaker.performOpeningHandshake(ctx.getChannel(), req);
} }
} }
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
// Check for closing frame // Check for closing frame
if (frame instanceof CloseWebSocketFrame) { if (frame instanceof CloseWebSocketFrame) {
this.handshaker.performClosingHandshake(ctx.getChannel(), (CloseWebSocketFrame) frame); this.handshaker.performClosingHandshake(ctx.getChannel(), (CloseWebSocketFrame) frame);
return; return;
} else if (frame instanceof PingWebSocketFrame) { } else if (frame instanceof PingWebSocketFrame) {
ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData())); ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData()));
return; return;
} else if (!(frame instanceof TextWebSocketFrame)) { } else if (!(frame instanceof TextWebSocketFrame)) {
throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass() throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
.getName())); .getName()));
} }
// Send the uppercase string back. // Send the uppercase string back.
String request = ((TextWebSocketFrame) frame).getText(); String request = ((TextWebSocketFrame) frame).getText();
logger.debug(String.format("Channel %s received %s", ctx.getChannel().getId(), request)); logger.debug(String.format("Channel %s received %s", ctx.getChannel().getId(), request));
ctx.getChannel().write(new TextWebSocketFrame(request.toUpperCase())); ctx.getChannel().write(new TextWebSocketFrame(request.toUpperCase()));
} }
private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) { private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
// Generate an error page if response status code is not OK (200). // Generate an error page if response status code is not OK (200).
if (res.getStatus().getCode() != 200) { if (res.getStatus().getCode() != 200) {
res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8)); res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8));
setContentLength(res, res.getContent().readableBytes()); setContentLength(res, res.getContent().readableBytes());
} }
// Send the response and close the connection if necessary. // Send the response and close the connection if necessary.
ChannelFuture f = ctx.getChannel().write(res); ChannelFuture f = ctx.getChannel().write(res);
if (!isKeepAlive(req) || res.getStatus().getCode() != 200) { if (!isKeepAlive(req) || res.getStatus().getCode() != 200) {
f.addListener(ChannelFutureListener.CLOSE); f.addListener(ChannelFutureListener.CLOSE);
} }
} }
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
e.getCause().printStackTrace(); e.getCause().printStackTrace();
e.getChannel().close(); e.getChannel().close();
} }
private String getWebSocketLocation(HttpRequest req) { private String getWebSocketLocation(HttpRequest req) {
return "ws://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH; return "ws://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH;
} }
} }

View File

@ -24,69 +24,69 @@ import org.jboss.netty.util.CharsetUtil;
*/ */
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 return ChannelBuffers
.copiedBuffer( .copiedBuffer(
"<html><head><title>Web Socket Test</title></head>" "<html><head><title>Web Socket Test</title></head>"
+ NEWLINE + NEWLINE
+ "<body>" + "<body>"
+ NEWLINE + NEWLINE
+ "<script type=\"text/javascript\">" + "<script type=\"text/javascript\">"
+ NEWLINE + NEWLINE
+ "var socket;" + "var socket;"
+ NEWLINE + NEWLINE
+ "if (!window.WebSocket) {" + "if (!window.WebSocket) {"
+ NEWLINE + NEWLINE
+ " window.WebSocket = window.MozWebSocket;" + " window.WebSocket = window.MozWebSocket;"
+ NEWLINE + NEWLINE
+ "}" + "}"
+ NEWLINE + NEWLINE
+ "if (window.WebSocket) {" + "if (window.WebSocket) {"
+ NEWLINE + NEWLINE
+ " socket = new WebSocket(\"" + " socket = new WebSocket(\""
+ webSocketLocation + webSocketLocation
+ "\");" + "\");"
+ NEWLINE + NEWLINE
+ " socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };" + " socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };"
+ NEWLINE + NEWLINE
+ " socket.onopen = function(event) { var ta = document.getElementById('responseText'); ta.value = \"Web Socket opened!\"; };" + " 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\"; };" + " socket.onclose = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + \"Web Socket closed\"; };"
+ NEWLINE + NEWLINE
+ "} else {" + "} else {"
+ NEWLINE + NEWLINE
+ " alert(\"Your browser does not support Web Socket.\");" + " alert(\"Your browser does not support Web Socket.\");"
+ NEWLINE + NEWLINE
+ "}" + "}"
+ NEWLINE + NEWLINE
+ NEWLINE + NEWLINE
+ "function send(message) {" + "function send(message) {"
+ NEWLINE + NEWLINE
+ " if (!window.WebSocket) { return; }" + " if (!window.WebSocket) { return; }"
+ NEWLINE + NEWLINE
+ " if (socket.readyState == WebSocket.OPEN) {" + " if (socket.readyState == WebSocket.OPEN) {"
+ NEWLINE + NEWLINE
+ " socket.send(message);" + " socket.send(message);"
+ NEWLINE + NEWLINE
+ " } else {" + " } else {"
+ NEWLINE + NEWLINE
+ " alert(\"The socket is not open.\");" + " alert(\"The socket is not open.\");"
+ NEWLINE + NEWLINE
+ " }" + " }"
+ NEWLINE + NEWLINE
+ "}" + "}"
+ NEWLINE + NEWLINE
+ "</script>" + "</script>"
+ NEWLINE + NEWLINE
+ "<form onsubmit=\"return false;\">" + "<form onsubmit=\"return false;\">"
+ NEWLINE + NEWLINE
+ "<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>" + "<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>"
+ "<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />" + "<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />"
+ NEWLINE + "<h3>Output</h3>" + NEWLINE + NEWLINE + "<h3>Output</h3>" + NEWLINE
+ "<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>" + "<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>"
+ NEWLINE + "</form>" + NEWLINE + "</body>" + NEWLINE + "</html>" + NEWLINE, + NEWLINE + "</form>" + NEWLINE + "</body>" + NEWLINE + "</html>" + NEWLINE,
CharsetUtil.US_ASCII); CharsetUtil.US_ASCII);
} }
} }

View File

@ -26,13 +26,13 @@ import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
/** /**
*/ */
public class WebSocketServerPipelineFactory implements ChannelPipelineFactory { public class WebSocketServerPipelineFactory 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();
pipeline.addLast("decoder", new HttpRequestDecoder()); pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpChunkAggregator(65536)); pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
pipeline.addLast("encoder", new HttpResponseEncoder()); pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("handler", new WebSocketServerHandler()); pipeline.addLast("handler", new WebSocketServerHandler());
return pipeline; return pipeline;
} }
} }

View File

@ -36,45 +36,42 @@ import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
* *
* <ul> * <ul>
* <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00) * <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00)
* <li>
* <li>Chrome 6-13 (draft-ietf-hybi-thewebsocketprotocol-00) * <li>Chrome 6-13 (draft-ietf-hybi-thewebsocketprotocol-00)
* <li>
* <li>Chrome 14+ (draft-ietf-hybi-thewebsocketprotocol-10) * <li>Chrome 14+ (draft-ietf-hybi-thewebsocketprotocol-10)
* <li> * <li>Chrome 16+ (RFC 6455 aka draft-ietf-hybi-thewebsocketprotocol-17)
* <li>Firefox 7+ (draft-ietf-hybi-thewebsocketprotocol-10) * <li>Firefox 7+ (draft-ietf-hybi-thewebsocketprotocol-10)
* <li>
* </ul> * </ul>
*/ */
public class WebSocketSslServer { public class WebSocketSslServer {
public static void main(String[] args) { public static void main(String[] args) {
ConsoleHandler ch = new ConsoleHandler(); ConsoleHandler ch = new ConsoleHandler();
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!");
System.exit(1); System.exit(1);
} }
// 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()));
// Set up the event pipeline factory. // Set up the event pipeline factory.
bootstrap.setPipelineFactory(new WebSocketSslServerPipelineFactory()); bootstrap.setPipelineFactory(new WebSocketSslServerPipelineFactory());
// 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 System.out
.println("Web Socket Server started on 8081. Open your browser and navigate to https://localhost:8081/"); .println("Web Socket Server started on 8081. Open your browser and navigate to https://localhost:8081/");
} }
} }

View File

@ -48,99 +48,99 @@ import org.jboss.netty.util.CharsetUtil;
* Handles handshakes and messages * Handles handshakes and messages
*/ */
public class WebSocketSslServerHandler extends SimpleChannelUpstreamHandler { public class WebSocketSslServerHandler extends SimpleChannelUpstreamHandler {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketSslServerHandler.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketSslServerHandler.class);
private static final String WEBSOCKET_PATH = "/websocket"; private static final String WEBSOCKET_PATH = "/websocket";
private WebSocketServerHandshaker handshaker = null; private WebSocketServerHandshaker handshaker = null;
@Override @Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
Object msg = e.getMessage(); Object msg = e.getMessage();
if (msg instanceof HttpRequest) { if (msg instanceof HttpRequest) {
handleHttpRequest(ctx, (HttpRequest) msg); handleHttpRequest(ctx, (HttpRequest) msg);
} else if (msg instanceof WebSocketFrame) { } else if (msg instanceof WebSocketFrame) {
handleWebSocketFrame(ctx, (WebSocketFrame) msg); handleWebSocketFrame(ctx, (WebSocketFrame) msg);
} }
} }
private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception { private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
// Allow only GET methods. // Allow only GET methods.
if (req.getMethod() != GET) { if (req.getMethod() != GET) {
sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN)); sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN));
return; return;
} }
// Send the demo page and favicon.ico // Send the demo page and favicon.ico
if (req.getUri().equals("/")) { if (req.getUri().equals("/")) {
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, OK); HttpResponse res = new DefaultHttpResponse(HTTP_1_1, OK);
ChannelBuffer content = WebSocketSslServerIndexPage.getContent(getWebSocketLocation(req)); ChannelBuffer content = WebSocketSslServerIndexPage.getContent(getWebSocketLocation(req));
res.setHeader(CONTENT_TYPE, "text/html; charset=UTF-8"); res.setHeader(CONTENT_TYPE, "text/html; charset=UTF-8");
setContentLength(res, content.readableBytes()); setContentLength(res, content.readableBytes());
res.setContent(content); res.setContent(content);
sendHttpResponse(ctx, req, res); sendHttpResponse(ctx, req, res);
return; return;
} else if (req.getUri().equals("/favicon.ico")) { } else if (req.getUri().equals("/favicon.ico")) {
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND); HttpResponse res = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND);
sendHttpResponse(ctx, req, res); sendHttpResponse(ctx, req, res);
return; return;
} }
// Handshake // Handshake
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory( WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
this.getWebSocketLocation(req), null, false); this.getWebSocketLocation(req), null, false);
this.handshaker = wsFactory.newHandshaker(req); this.handshaker = wsFactory.newHandshaker(req);
if (this.handshaker == null) { if (this.handshaker == null) {
wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel()); wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel());
} else { } else {
this.handshaker.performOpeningHandshake(ctx.getChannel(), req); this.handshaker.performOpeningHandshake(ctx.getChannel(), req);
} }
} }
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
// Check for closing frame // Check for closing frame
if (frame instanceof CloseWebSocketFrame) { if (frame instanceof CloseWebSocketFrame) {
this.handshaker.performClosingHandshake(ctx.getChannel(), (CloseWebSocketFrame) frame); this.handshaker.performClosingHandshake(ctx.getChannel(), (CloseWebSocketFrame) frame);
return; return;
} else if (frame instanceof PingWebSocketFrame) { } else if (frame instanceof PingWebSocketFrame) {
ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData())); ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData()));
return; return;
} else if (!(frame instanceof TextWebSocketFrame)) { } else if (!(frame instanceof TextWebSocketFrame)) {
throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass() throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
.getName())); .getName()));
} }
// Send the uppercase string back. // Send the uppercase string back.
String request = ((TextWebSocketFrame) frame).getText(); String request = ((TextWebSocketFrame) frame).getText();
logger.debug(String.format("Channel %s received %s", ctx.getChannel().getId(), request)); logger.debug(String.format("Channel %s received %s", ctx.getChannel().getId(), request));
ctx.getChannel().write(new TextWebSocketFrame(request.toUpperCase())); ctx.getChannel().write(new TextWebSocketFrame(request.toUpperCase()));
} }
private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) { private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
// Generate an error page if response status code is not OK (200). // Generate an error page if response status code is not OK (200).
if (res.getStatus().getCode() != 200) { if (res.getStatus().getCode() != 200) {
res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8)); res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8));
setContentLength(res, res.getContent().readableBytes()); setContentLength(res, res.getContent().readableBytes());
} }
// Send the response and close the connection if necessary. // Send the response and close the connection if necessary.
ChannelFuture f = ctx.getChannel().write(res); ChannelFuture f = ctx.getChannel().write(res);
if (!isKeepAlive(req) || res.getStatus().getCode() != 200) { if (!isKeepAlive(req) || res.getStatus().getCode() != 200) {
f.addListener(ChannelFutureListener.CLOSE); f.addListener(ChannelFutureListener.CLOSE);
} }
} }
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
e.getCause().printStackTrace(); e.getCause().printStackTrace();
e.getChannel().close(); e.getChannel().close();
} }
private String getWebSocketLocation(HttpRequest req) { private String getWebSocketLocation(HttpRequest req) {
return "wss://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH; return "wss://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH;
} }
} }

View File

@ -24,69 +24,69 @@ import org.jboss.netty.util.CharsetUtil;
*/ */
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 return ChannelBuffers
.copiedBuffer( .copiedBuffer(
"<html><head><title>Web Socket Test</title></head>" "<html><head><title>Web Socket Test</title></head>"
+ NEWLINE + NEWLINE
+ "<body>" + "<body>"
+ NEWLINE + NEWLINE
+ "<script type=\"text/javascript\">" + "<script type=\"text/javascript\">"
+ NEWLINE + NEWLINE
+ "var socket;" + "var socket;"
+ NEWLINE + NEWLINE
+ "if (!window.WebSocket) {" + "if (!window.WebSocket) {"
+ NEWLINE + NEWLINE
+ " window.WebSocket = window.MozWebSocket;" + " window.WebSocket = window.MozWebSocket;"
+ NEWLINE + NEWLINE
+ "}" + "}"
+ NEWLINE + NEWLINE
+ "if (window.WebSocket) {" + "if (window.WebSocket) {"
+ NEWLINE + NEWLINE
+ " socket = new WebSocket(\"" + " socket = new WebSocket(\""
+ webSocketLocation + webSocketLocation
+ "\");" + "\");"
+ NEWLINE + NEWLINE
+ " socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };" + " socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };"
+ NEWLINE + NEWLINE
+ " socket.onopen = function(event) { var ta = document.getElementById('responseText'); ta.value = \"Web Socket opened!\"; };" + " 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\"; };" + " socket.onclose = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + \"Web Socket closed\"; };"
+ NEWLINE + NEWLINE
+ "} else {" + "} else {"
+ NEWLINE + NEWLINE
+ " alert(\"Your browser does not support Web Socket.\");" + " alert(\"Your browser does not support Web Socket.\");"
+ NEWLINE + NEWLINE
+ "}" + "}"
+ NEWLINE + NEWLINE
+ NEWLINE + NEWLINE
+ "function send(message) {" + "function send(message) {"
+ NEWLINE + NEWLINE
+ " if (!window.WebSocket) { return; }" + " if (!window.WebSocket) { return; }"
+ NEWLINE + NEWLINE
+ " if (socket.readyState == WebSocket.OPEN) {" + " if (socket.readyState == WebSocket.OPEN) {"
+ NEWLINE + NEWLINE
+ " socket.send(message);" + " socket.send(message);"
+ NEWLINE + NEWLINE
+ " } else {" + " } else {"
+ NEWLINE + NEWLINE
+ " alert(\"The socket is not open.\");" + " alert(\"The socket is not open.\");"
+ NEWLINE + NEWLINE
+ " }" + " }"
+ NEWLINE + NEWLINE
+ "}" + "}"
+ NEWLINE + NEWLINE
+ "</script>" + "</script>"
+ NEWLINE + NEWLINE
+ "<form onsubmit=\"return false;\">" + "<form onsubmit=\"return false;\">"
+ NEWLINE + NEWLINE
+ "<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>" + "<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>"
+ "<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />" + "<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />"
+ NEWLINE + "<h3>Output</h3>" + NEWLINE + NEWLINE + "<h3>Output</h3>" + NEWLINE
+ "<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>" + "<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>"
+ NEWLINE + "</form>" + NEWLINE + "</body>" + NEWLINE + "</html>" + NEWLINE, + NEWLINE + "</form>" + NEWLINE + "</body>" + NEWLINE + "</html>" + NEWLINE,
CharsetUtil.US_ASCII); CharsetUtil.US_ASCII);
} }
} }

View File

@ -29,18 +29,18 @@ import org.jboss.netty.handler.ssl.SslHandler;
/** /**
*/ */
public class WebSocketSslServerPipelineFactory implements ChannelPipelineFactory { 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));
pipeline.addLast("encoder", new HttpResponseEncoder()); pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("handler", new WebSocketSslServerHandler()); pipeline.addLast("handler", new WebSocketSslServerHandler());
return pipeline; return pipeline;
} }
} }

View File

@ -30,73 +30,73 @@ import org.jboss.netty.logging.InternalLoggerFactory;
*/ */
public class WebSocketSslServerSslContext { public class WebSocketSslServerSslContext {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketSslServerSslContext.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketSslServerSslContext.class);
private static final String PROTOCOL = "TLS"; private static final String PROTOCOL = "TLS";
private SSLContext _serverContext; private SSLContext _serverContext;
/** /**
* Returns the singleton instance for this class * Returns the singleton instance for this class
*/ */
public static WebSocketSslServerSslContext getInstance() { public static WebSocketSslServerSslContext getInstance() {
return SingletonHolder.INSTANCE; return SingletonHolder.INSTANCE;
} }
/** /**
* SingletonHolder is loaded on the first execution of Singleton.getInstance() or the first access to * SingletonHolder is loaded on the first execution of Singleton.getInstance() or the first access to
* SingletonHolder.INSTANCE, not before. * SingletonHolder.INSTANCE, not before.
* *
* See http://en.wikipedia.org/wiki/Singleton_pattern * See http://en.wikipedia.org/wiki/Singleton_pattern
*/ */
private static class SingletonHolder { private static class SingletonHolder {
public static final WebSocketSslServerSslContext INSTANCE = new WebSocketSslServerSslContext(); public static final WebSocketSslServerSslContext INSTANCE = new WebSocketSslServerSslContext();
} }
/** /**
* Constructor for singleton * Constructor for singleton
*/ */
private WebSocketSslServerSslContext() { private WebSocketSslServerSslContext() {
try { try {
// Key store (Server side certificate) // Key store (Server side certificate)
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) { if (algorithm == null) {
algorithm = "SunX509"; algorithm = "SunX509";
} }
SSLContext serverContext = null; SSLContext serverContext = null;
try { try {
String keyStoreFilePath = System.getProperty("keystore.file.path"); String keyStoreFilePath = System.getProperty("keystore.file.path");
String keyStoreFilePassword = System.getProperty("keystore.file.password"); String keyStoreFilePassword = System.getProperty("keystore.file.password");
KeyStore ks = KeyStore.getInstance("JKS"); KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream fin = new FileInputStream(keyStoreFilePath); FileInputStream fin = new FileInputStream(keyStoreFilePath);
ks.load(fin, keyStoreFilePassword.toCharArray()); ks.load(fin, keyStoreFilePassword.toCharArray());
// Set up key manager factory to use our key store // Set up key manager factory to use our key store
// Assume key password is the same as the key store file // Assume key password is the same as the key store file
// password // password
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
kmf.init(ks, keyStoreFilePassword.toCharArray()); kmf.init(ks, keyStoreFilePassword.toCharArray());
// Initialise the SSLContext to work with our key managers. // Initialise the SSLContext to work with our key managers.
serverContext = SSLContext.getInstance(PROTOCOL); serverContext = SSLContext.getInstance(PROTOCOL);
serverContext.init(kmf.getKeyManagers(), null, null); serverContext.init(kmf.getKeyManagers(), null, null);
} catch (Exception e) { } catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext", e); throw new Error("Failed to initialize the server-side SSLContext", e);
} }
_serverContext = serverContext; _serverContext = serverContext;
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Error initializing SslContextManager. " + ex.getMessage(), ex); logger.error("Error initializing SslContextManager. " + ex.getMessage(), ex);
System.exit(1); System.exit(1);
} }
} }
/** /**
* Returns the server context with server side key store * Returns the server context with server side key store
*/ */
public SSLContext getServerContext() { public SSLContext getServerContext() {
return _serverContext; return _serverContext;
} }
} }

View File

@ -23,42 +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 fragment flag is set to true. * Creates a new binary frame with the specified binary data. The final 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 fragment flag. * Creates a new binary frame with the specified binary data and the final 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

@ -25,114 +25,114 @@ import org.jboss.netty.util.CharsetUtil;
*/ */
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 final fragment flag is set to true. * Creates a new continuation frame with the specified binary data. The 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 of a fragmented text message * Aggregated text set by decoder on the final continuation frame 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 fragmented text message * Aggregated text returned by decoder on the final continuation frame of a 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,98 +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 fragment flag is set to true. * Creates a new text frame with the specified text string. The final 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 fragment flag is set to true. * Creates a new text frame with the specified binary data. The final 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 fragment flag is set to true. * Creates a new text frame with the specified text string. The final 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 fragment flag is set to true. * Creates a new text frame with the specified binary data. The final 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,59 +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, 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, 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, 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, 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, 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, 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, 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 }; 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, 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, 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, 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, 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, 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

@ -33,101 +33,101 @@ import org.jboss.netty.handler.codec.replay.VoidEnum;
*/ */
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 specified {@code maxFrameSize}. If the client * Creates a new instance of {@code WebSocketFrameDecoder} with the specified {@code maxFrameSize}. If the client
* sends a frame size larger than {@code maxFrameSize}, the channel will be closed. * sends a frame size larger 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) protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, VoidEnum 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;
} }
// 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

@ -33,65 +33,65 @@ import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
@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() ChannelBuffer encoded = channel.getConfig().getBufferFactory()
.getBuffer(data.order(), data.readableBytes() + 2); .getBuffer(data.order(), data.readableBytes() + 2);
encoded.writeByte((byte) 0x00); encoded.writeByte((byte) 0x00);
encoded.writeBytes(data, data.readerIndex(), data.readableBytes()); encoded.writeBytes(data, data.readerIndex(), data.readableBytes());
encoded.writeByte((byte) 0xFF); encoded.writeByte((byte) 0xFF);
return encoded; return encoded;
} else if (frame instanceof CloseWebSocketFrame) { } else if (frame instanceof CloseWebSocketFrame) {
// Close frame // Close frame
ChannelBuffer data = frame.getBinaryData(); ChannelBuffer data = frame.getBinaryData();
ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), 2); ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), 2);
encoded.writeByte((byte) 0xFF); encoded.writeByte((byte) 0xFF);
encoded.writeByte((byte) 0x00); encoded.writeByte((byte) 0x00);
return encoded; return encoded;
} else { } else {
// Binary frame // Binary frame
ChannelBuffer data = frame.getBinaryData(); ChannelBuffer data = frame.getBinaryData();
int dataLen = data.readableBytes(); int dataLen = data.readableBytes();
ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), dataLen + 5); 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

@ -55,325 +55,325 @@ import org.jboss.netty.logging.InternalLoggerFactory;
*/ */
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 masked payload. Client implementations * Web socket servers must set this to true processed incoming 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

@ -57,117 +57,117 @@ import org.jboss.netty.logging.InternalLoggerFactory;
*/ */
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. Server implementations must set this to * Web socket clients must set this to true to mask payload. 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; int b0 = 0;
if (frame.isFinalFragment()) { if (frame.isFinalFragment()) {
b0 |= (1 << 7); b0 |= (1 << 7);
} }
b0 |= (frame.getRsv() % 8) << 4; b0 |= (frame.getRsv() % 8) << 4;
b0 |= opcode % 128; b0 |= opcode % 128;
ChannelBuffer header; ChannelBuffer header;
ChannelBuffer body; ChannelBuffer body;
if (opcode == OPCODE_PING && length > 125) { if (opcode == OPCODE_PING && length > 125) {
throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was " throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was "
+ length); + length);
} }
int maskLength = this.maskPayload ? 4 : 0; int maskLength = this.maskPayload ? 4 : 0;
if (length <= 125) { if (length <= 125) {
header = ChannelBuffers.buffer(2 + maskLength); header = ChannelBuffers.buffer(2 + maskLength);
header.writeByte(b0); header.writeByte(b0);
byte b = (byte) (this.maskPayload ? (0x80 | (byte) length) : (byte) length); byte b = (byte) (this.maskPayload ? (0x80 | (byte) length) : (byte) length);
header.writeByte(b); header.writeByte(b);
} else if (length <= 0xFFFF) { } else if (length <= 0xFFFF) {
header = ChannelBuffers.buffer(4 + maskLength); header = ChannelBuffers.buffer(4 + maskLength);
header.writeByte(b0); header.writeByte(b0);
header.writeByte(this.maskPayload ? (0xFE) : 126); header.writeByte(this.maskPayload ? (0xFE) : 126);
header.writeByte((length >>> 8) & 0xFF); header.writeByte((length >>> 8) & 0xFF);
header.writeByte((length) & 0xFF); header.writeByte((length) & 0xFF);
} else { } else {
header = ChannelBuffers.buffer(10 + maskLength); header = ChannelBuffers.buffer(10 + maskLength);
header.writeByte(b0); header.writeByte(b0);
header.writeByte(this.maskPayload ? (0xFF) : 127); header.writeByte(this.maskPayload ? (0xFF) : 127);
header.writeLong(length); header.writeLong(length);
} }
// Write payload // Write payload
if (this.maskPayload) { if (this.maskPayload) {
Integer random = (int) (Math.random() * Integer.MAX_VALUE); Integer random = (int) (Math.random() * Integer.MAX_VALUE);
mask = ByteBuffer.allocate(4).putInt(random).array(); mask = ByteBuffer.allocate(4).putInt(random).array();
header.writeBytes(mask); header.writeBytes(mask);
body = ChannelBuffers.buffer(length); body = ChannelBuffers.buffer(length);
int counter = 0; int counter = 0;
while (data.readableBytes() > 0) { while (data.readableBytes() > 0) {
byte byteData = data.readByte(); byte byteData = data.readByte();
body.writeByte(byteData ^ mask[+counter++ % 4]); body.writeByte(byteData ^ mask[+counter++ % 4]);
} }
} else { } else {
body = data; body = data;
} }
return ChannelBuffers.wrappedBuffer(header, body); return ChannelBuffers.wrappedBuffer(header, body);
} }
// If not websocket, then just return the message // If not websocket, then just return the message
return msg; return msg;
} }
} }

View File

@ -43,16 +43,16 @@ package org.jboss.netty.handler.codec.http.websocketx;
*/ */
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 masked payload. Client implementations * Web socket servers must set this to true processed incoming 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

@ -45,14 +45,14 @@ package org.jboss.netty.handler.codec.http.websocketx;
*/ */
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. Server implementations must set this to * Web socket clients must set this to true to mask payload. Server implementations must set this to
* false. * false.
*/ */
public WebSocket13FrameEncoder(boolean maskPayload) { public WebSocket13FrameEncoder(boolean maskPayload) {
super(maskPayload); super(maskPayload);
} }
} }

View File

@ -32,184 +32,184 @@ import org.jboss.netty.util.CharsetUtil;
*/ */
public abstract class WebSocketClientHandshaker { public abstract class WebSocketClientHandshaker {
private URI webSocketURL; private URI webSocketURL;
private WebSocketVersion version = WebSocketVersion.UNKNOWN; private WebSocketVersion version = WebSocketVersion.UNKNOWN;
private boolean openingHandshakeCompleted = false; private boolean openingHandshakeCompleted = false;
private String subProtocolRequest = null; private String subProtocolRequest = null;
private String subProtocolResponse = null; private String subProtocolResponse = null;
protected Map<String, String> customHeaders = null; protected Map<String, String> customHeaders = null;
/** /**
* Base constructor * Base constructor
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "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 server * Version of web socket specification to use to connect to the server
* @param subProtocol * @param subProtocol
* Sub protocol request sent to the server. * Sub protocol request sent to the server.
* @param customHeaders * @param customHeaders
* Map of custom headers to add to the client request * Map of custom headers to add to the client request
*/ */
public WebSocketClientHandshaker(URI webSocketURL, WebSocketVersion version, String subProtocol, public WebSocketClientHandshaker(URI webSocketURL, WebSocketVersion version, String subProtocol,
Map<String, String> customHeaders) { Map<String, String> customHeaders) {
this.webSocketURL = webSocketURL; this.webSocketURL = webSocketURL;
this.version = version; this.version = version;
this.subProtocolRequest = subProtocol; this.subProtocolRequest = subProtocol;
this.customHeaders = customHeaders; this.customHeaders = customHeaders;
} }
/** /**
* Returns the URI to the web socket. e.g. "ws://myhost.com/path" * Returns the URI to the web socket. e.g. "ws://myhost.com/path"
*/ */
public URI getWebSocketURL() { public URI getWebSocketURL() {
return webSocketURL; return webSocketURL;
} }
protected void setWebSocketURL(URI webSocketURL) { protected void setWebSocketURL(URI webSocketURL) {
this.webSocketURL = webSocketURL; this.webSocketURL = webSocketURL;
} }
/** /**
* Version of the web socket specification that is being used * Version of the web socket specification that is being used
*/ */
public WebSocketVersion getVersion() { public WebSocketVersion getVersion() {
return version; return version;
} }
protected void setVersion(WebSocketVersion version) { protected void setVersion(WebSocketVersion version) {
this.version = version; this.version = version;
} }
/** /**
* Flag to indicate if the opening handshake is complete * Flag to indicate if the opening handshake is complete
*/ */
public boolean isOpeningHandshakeCompleted() { public boolean isOpeningHandshakeCompleted() {
return openingHandshakeCompleted; return openingHandshakeCompleted;
} }
protected void setOpenningHandshakeCompleted(boolean openningHandshakeCompleted) { protected void setOpenningHandshakeCompleted(boolean openningHandshakeCompleted) {
this.openingHandshakeCompleted = openningHandshakeCompleted; this.openingHandshakeCompleted = openningHandshakeCompleted;
} }
/** /**
* Returns the sub protocol request sent to the server as specified in the constructor * Returns the sub protocol request sent to the server as specified in the constructor
*/ */
public String getSubProtocolRequest() { public String getSubProtocolRequest() {
return subProtocolRequest; return subProtocolRequest;
} }
protected void setSubProtocolRequest(String subProtocolRequest) { protected void setSubProtocolRequest(String subProtocolRequest) {
this.subProtocolRequest = subProtocolRequest; this.subProtocolRequest = subProtocolRequest;
} }
/** /**
* Returns the sub protocol response and sent by the server. Only available after end of handshake. * Returns the sub protocol response and sent by the server. Only available after end of handshake.
*/ */
public String getSubProtocolResponse() { public String getSubProtocolResponse() {
return subProtocolResponse; return subProtocolResponse;
} }
protected void setSubProtocolResponse(String subProtocolResponse) { protected void setSubProtocolResponse(String subProtocolResponse) {
this.subProtocolResponse = subProtocolResponse; this.subProtocolResponse = subProtocolResponse;
} }
/** /**
* Performs the opening handshake * Performs the opening handshake
* *
* @param channel * @param channel
* Channel * Channel
*/ */
public abstract void performOpeningHandshake(Channel channel); public abstract void performOpeningHandshake(Channel channel);
/** /**
* Performs the closing handshake * Performs the closing handshake
* *
* @param channel * @param channel
* Channel * Channel
* @param response * @param response
* HTTP response containing the closing handshake details * HTTP response containing the closing handshake details
*/ */
public abstract void performClosingHandshake(Channel channel, HttpResponse response) public abstract void performClosingHandshake(Channel channel, HttpResponse response)
throws WebSocketHandshakeException; throws WebSocketHandshakeException;
/** /**
* 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");
} }
} }
/** /**
* Performs an SHA-1 hash * Performs an SHA-1 hash
* *
* @param bytes * @param bytes
* Data to hash * Data to hash
* @return Hashed data * @return Hashed data
*/ */
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);
} }
/** /**
* Creates some random bytes * Creates some random bytes
* *
* @param size * @param size
* Number of random bytes to create * Number of random bytes to create
* @return random bytes * @return random bytes
*/ */
protected byte[] createRandomBytes(int size) { protected byte[] createRandomBytes(int size) {
byte[] bytes = new byte[size]; byte[] bytes = new byte[size];
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
bytes[i] = (byte) createRandomNumber(0, 255); bytes[i] = (byte) createRandomNumber(0, 255);
} }
return bytes; return bytes;
} }
/** /**
* Generates a random number * Generates a random number
* *
* @param min * @param min
* Minimum value * Minimum value
* @param max * @param max
* Maximum value * Maximum value
* @return Random number * @return Random number
*/ */
protected int createRandomNumber(int min, int max) { protected int createRandomNumber(int min, int max) {
return (int) (Math.random() * max + min); return (int) (Math.random() * max + min);
} }
} }

View File

@ -45,202 +45,202 @@ import org.jboss.netty.handler.codec.http.HttpVersion;
*/ */
public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker { public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
private byte[] expectedChallengeResponseBytes = null; private byte[] expectedChallengeResponseBytes = null;
/** /**
* Constructor specifying the destination web socket location and version to initiate * Constructor specifying the destination web socket location and version to initiate
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "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 server * Version of web socket specification to use to connect to the server
* @param subProtocol * @param subProtocol
* Sub protocol request sent to the server. * Sub protocol request sent to the server.
* @param customHeaders * @param customHeaders
* Map of custom headers to add to the client request * Map of custom headers to add to the client request
*/ */
public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subProtocol, public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subProtocol,
Map<String, String> customHeaders) { Map<String, String> customHeaders) {
super(webSocketURL, version, subProtocol, customHeaders); super(webSocketURL, version, subProtocol, customHeaders);
} }
/** /**
* <p> * <p>
* Sends the opening request to the server: * Sends the opening request to the server:
* </p> * </p>
* *
* <pre> * <pre>
* GET /demo HTTP/1.1 * GET /demo HTTP/1.1
* Upgrade: WebSocket * Upgrade: WebSocket
* Connection: Upgrade * Connection: Upgrade
* Host: example.com * Host: example.com
* Origin: http://example.com * Origin: http://example.com
* Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 * Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
* Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 * Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
* *
* ^n:ds[4U * ^n:ds[4U
* </pre> * </pre>
* *
* @param channel * @param channel
* Channel into which we can write our request * Channel into which we can write our request
*/ */
@Override @Override
public void performOpeningHandshake(Channel channel) { public void performOpeningHandshake(Channel channel) {
// Make keys // Make keys
int spaces1 = createRandomNumber(1, 12); int spaces1 = createRandomNumber(1, 12);
int spaces2 = createRandomNumber(1, 12); int spaces2 = createRandomNumber(1, 12);
int max1 = Integer.MAX_VALUE / spaces1; int max1 = Integer.MAX_VALUE / spaces1;
int max2 = Integer.MAX_VALUE / spaces2; int max2 = Integer.MAX_VALUE / spaces2;
int number1 = createRandomNumber(0, max1); int number1 = createRandomNumber(0, max1);
int number2 = createRandomNumber(0, max2); int number2 = createRandomNumber(0, max2);
int product1 = number1 * spaces1; int product1 = number1 * spaces1;
int product2 = number2 * spaces2; int product2 = number2 * spaces2;
String key1 = Integer.toString(product1); String key1 = Integer.toString(product1);
String key2 = Integer.toString(product2); String key2 = Integer.toString(product2);
key1 = insertRandomCharacters(key1); key1 = insertRandomCharacters(key1);
key2 = insertRandomCharacters(key2); key2 = insertRandomCharacters(key2);
key1 = insertSpaces(key1, spaces1); key1 = insertSpaces(key1, spaces1);
key2 = insertSpaces(key2, spaces2); key2 = insertSpaces(key2, spaces2);
byte[] key3 = createRandomBytes(8); byte[] key3 = createRandomBytes(8);
ByteBuffer buffer = ByteBuffer.allocate(4); ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.putInt(number1); buffer.putInt(number1);
byte[] number1Array = buffer.array(); byte[] number1Array = buffer.array();
buffer = ByteBuffer.allocate(4); buffer = ByteBuffer.allocate(4);
buffer.putInt(number2); buffer.putInt(number2);
byte[] number2Array = buffer.array(); byte[] number2Array = buffer.array();
byte[] challenge = new byte[16]; byte[] challenge = new byte[16];
System.arraycopy(number1Array, 0, challenge, 0, 4); System.arraycopy(number1Array, 0, challenge, 0, 4);
System.arraycopy(number2Array, 0, challenge, 4, 4); System.arraycopy(number2Array, 0, challenge, 4, 4);
System.arraycopy(key3, 0, challenge, 8, 8); System.arraycopy(key3, 0, challenge, 8, 8);
this.expectedChallengeResponseBytes = md5(challenge); this.expectedChallengeResponseBytes = md5(challenge);
// Get path // Get path
URI wsURL = this.getWebSocketURL(); URI wsURL = this.getWebSocketURL();
String path = wsURL.getPath(); String path = wsURL.getPath();
if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) { if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
path = wsURL.getPath() + "?" + wsURL.getQuery(); path = wsURL.getPath() + "?" + wsURL.getQuery();
} }
// Format request // Format request
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path); HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
request.addHeader(Names.UPGRADE, Values.WEBSOCKET); request.addHeader(Names.UPGRADE, Values.WEBSOCKET);
request.addHeader(Names.CONNECTION, Values.UPGRADE); request.addHeader(Names.CONNECTION, Values.UPGRADE);
request.addHeader(Names.HOST, wsURL.getHost()); request.addHeader(Names.HOST, wsURL.getHost());
request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost()); request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost());
request.addHeader(Names.SEC_WEBSOCKET_KEY1, key1); request.addHeader(Names.SEC_WEBSOCKET_KEY1, key1);
request.addHeader(Names.SEC_WEBSOCKET_KEY2, key2); request.addHeader(Names.SEC_WEBSOCKET_KEY2, key2);
if (this.getSubProtocolRequest() != null && !this.getSubProtocolRequest().equals("")) { if (this.getSubProtocolRequest() != null && !this.getSubProtocolRequest().equals("")) {
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.getSubProtocolRequest()); request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.getSubProtocolRequest());
} }
if (customHeaders != null) { if (customHeaders != null) {
for (String header : customHeaders.keySet()) { for (String header : customHeaders.keySet()) {
request.addHeader(header, customHeaders.get(header)); request.addHeader(header, customHeaders.get(header));
} }
} }
request.setContent(ChannelBuffers.copiedBuffer(key3)); request.setContent(ChannelBuffers.copiedBuffer(key3));
channel.write(request); channel.write(request);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket00FrameEncoder()); channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket00FrameEncoder());
} }
/** /**
* <p> * <p>
* Process server response: * Process server response:
* </p> * </p>
* *
* <pre> * <pre>
* HTTP/1.1 101 WebSocket Protocol Handshake * HTTP/1.1 101 WebSocket Protocol Handshake
* Upgrade: WebSocket * Upgrade: WebSocket
* Connection: Upgrade * Connection: Upgrade
* Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Origin: http://example.com
* Sec-WebSocket-Location: ws://example.com/demo * Sec-WebSocket-Location: ws://example.com/demo
* Sec-WebSocket-Protocol: sample * Sec-WebSocket-Protocol: sample
* *
* 8jKS'y:G*Co,Wxa- * 8jKS'y:G*Co,Wxa-
* </pre> * </pre>
* *
* @param channel * @param channel
* Channel * Channel
* @param response * @param response
* HTTP response returned from the server for the request sent by beginOpeningHandshake00(). * HTTP response returned from the server for the request sent by beginOpeningHandshake00().
* @throws WebSocketHandshakeException * @throws WebSocketHandshakeException
*/ */
@Override @Override
public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException { public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
final HttpResponseStatus status = new HttpResponseStatus(101, "WebSocket Protocol Handshake"); final HttpResponseStatus status = new HttpResponseStatus(101, "WebSocket Protocol Handshake");
if (!response.getStatus().equals(status)) { if (!response.getStatus().equals(status)) {
throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus()); throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
} }
String upgrade = response.getHeader(Names.UPGRADE); String upgrade = response.getHeader(Names.UPGRADE);
if (upgrade == null || !upgrade.equals(Values.WEBSOCKET)) { if (upgrade == null || !upgrade.equals(Values.WEBSOCKET)) {
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
+ response.getHeader(Names.UPGRADE)); + response.getHeader(Names.UPGRADE));
} }
String connection = response.getHeader(Names.CONNECTION); String connection = response.getHeader(Names.CONNECTION);
if (connection == null || !connection.equals(Values.UPGRADE)) { if (connection == null || !connection.equals(Values.UPGRADE)) {
throw new WebSocketHandshakeException("Invalid handshake response connection: " throw new WebSocketHandshakeException("Invalid handshake response connection: "
+ response.getHeader(Names.CONNECTION)); + response.getHeader(Names.CONNECTION));
} }
byte[] challenge = response.getContent().array(); byte[] challenge = response.getContent().array();
if (!Arrays.equals(challenge, expectedChallengeResponseBytes)) { if (!Arrays.equals(challenge, expectedChallengeResponseBytes)) {
throw new WebSocketHandshakeException("Invalid challenge"); throw new WebSocketHandshakeException("Invalid challenge");
} }
String protocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); String protocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
this.setSubProtocolResponse(protocol); this.setSubProtocolResponse(protocol);
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket00FrameDecoder()); channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket00FrameDecoder());
this.setOpenningHandshakeCompleted(true); this.setOpenningHandshakeCompleted(true);
} }
private String insertRandomCharacters(String key) { private String insertRandomCharacters(String key) {
int count = createRandomNumber(1, 12); int count = createRandomNumber(1, 12);
char[] randomChars = new char[count]; char[] randomChars = new char[count];
int randCount = 0; int randCount = 0;
while (randCount < count) { while (randCount < count) {
int rand = (int) (Math.random() * 0x7e + 0x21); int rand = (int) (Math.random() * 0x7e + 0x21);
if (((0x21 < rand) && (rand < 0x2f)) || ((0x3a < rand) && (rand < 0x7e))) { if (((0x21 < rand) && (rand < 0x2f)) || ((0x3a < rand) && (rand < 0x7e))) {
randomChars[randCount] = (char) rand; randomChars[randCount] = (char) rand;
randCount += 1; randCount += 1;
} }
} }
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
int split = createRandomNumber(0, key.length()); int split = createRandomNumber(0, key.length());
String part1 = key.substring(0, split); String part1 = key.substring(0, split);
String part2 = key.substring(split); String part2 = key.substring(split);
key = part1 + randomChars[i] + part2; key = part1 + randomChars[i] + part2;
} }
return key; return key;
} }
private String insertSpaces(String key, int spaces) { private String insertSpaces(String key, int spaces) {
for (int i = 0; i < spaces; i++) { for (int i = 0; i < spaces; i++) {
int split = createRandomNumber(1, key.length() - 1); int split = createRandomNumber(1, key.length() - 1);
String part1 = key.substring(0, split); String part1 = key.substring(0, split);
String part2 = key.substring(split); String part2 = key.substring(split);
key = part1 + " " + part2; key = part1 + " " + part2;
} }
return key; return key;
} }
} }

View File

@ -42,150 +42,150 @@ import org.jboss.netty.util.CharsetUtil;
*/ */
public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker08.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker08.class);
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private String expectedChallengeResponseString = null; private String expectedChallengeResponseString = null;
private static final String protocol = null; private static final String protocol = null;
private boolean allowExtensions = false; private boolean allowExtensions = false;
/** /**
* Constructor specifying the destination web socket location and version to initiate * Constructor specifying the destination web socket location and version to initiate
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "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 server * Version of web socket specification to use to connect to the 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 socket frame * Allow extensions to be used in the reserved bits of the web 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
*/ */
public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subProtocol, public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subProtocol,
boolean allowExtensions, Map<String, String> customHeaders) { boolean allowExtensions, Map<String, String> customHeaders) {
super(webSocketURL, version, subProtocol, customHeaders); super(webSocketURL, version, subProtocol, customHeaders);
this.allowExtensions = allowExtensions; this.allowExtensions = allowExtensions;
} }
/** /**
* /** * /**
* <p> * <p>
* Sends the opening request to the server: * Sends the opening request to the server:
* </p> * </p>
* *
* <pre> * <pre>
* GET /chat HTTP/1.1 * GET /chat HTTP/1.1
* Host: server.example.com * Host: server.example.com
* Upgrade: websocket * Upgrade: websocket
* Connection: Upgrade * Connection: Upgrade
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
* Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Origin: http://example.com
* Sec-WebSocket-Protocol: chat, superchat * Sec-WebSocket-Protocol: chat, superchat
* Sec-WebSocket-Version: 8 * Sec-WebSocket-Version: 8
* </pre> * </pre>
* *
* @param channel * @param channel
* Channel into which we can write our request * Channel into which we can write our request
*/ */
@Override @Override
public void performOpeningHandshake(Channel channel) { public void performOpeningHandshake(Channel channel) {
// Get path // Get path
URI wsURL = this.getWebSocketURL(); URI wsURL = this.getWebSocketURL();
String path = wsURL.getPath(); String path = wsURL.getPath();
if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) { if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
path = wsURL.getPath() + "?" + wsURL.getQuery(); path = wsURL.getPath() + "?" + wsURL.getQuery();
} }
// Get 16 bit nonce and base 64 encode it // Get 16 bit nonce and base 64 encode it
byte[] nonce = createRandomBytes(16); byte[] nonce = createRandomBytes(16);
String key = base64Encode(nonce); String key = base64Encode(nonce);
String acceptSeed = key + MAGIC_GUID; String acceptSeed = key + MAGIC_GUID;
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII)); byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
this.expectedChallengeResponseString = base64Encode(sha1); this.expectedChallengeResponseString = base64Encode(sha1);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("WS Version 08 Client Handshake key: %s. Expected response: %s.", key, logger.debug(String.format("WS Version 08 Client Handshake key: %s. Expected response: %s.", key,
this.expectedChallengeResponseString)); this.expectedChallengeResponseString));
} }
// Format request // Format request
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path); HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
request.addHeader(Names.UPGRADE, Values.WEBSOCKET.toLowerCase()); request.addHeader(Names.UPGRADE, Values.WEBSOCKET.toLowerCase());
request.addHeader(Names.CONNECTION, Values.UPGRADE); request.addHeader(Names.CONNECTION, Values.UPGRADE);
request.addHeader(Names.SEC_WEBSOCKET_KEY, key); request.addHeader(Names.SEC_WEBSOCKET_KEY, key);
request.addHeader(Names.HOST, wsURL.getHost()); request.addHeader(Names.HOST, wsURL.getHost());
request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost()); request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost());
if (protocol != null && !protocol.equals("")) { if (protocol != null && !protocol.equals("")) {
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol); request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
} }
request.addHeader(Names.SEC_WEBSOCKET_VERSION, "8"); request.addHeader(Names.SEC_WEBSOCKET_VERSION, "8");
if (customHeaders != null) { if (customHeaders != null) {
for (String header : customHeaders.keySet()) { for (String header : customHeaders.keySet()) {
request.addHeader(header, customHeaders.get(header)); request.addHeader(header, customHeaders.get(header));
} }
} }
channel.write(request); channel.write(request);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket08FrameEncoder(true)); channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket08FrameEncoder(true));
} }
/** /**
* <p> * <p>
* Process server response: * Process server response:
* </p> * </p>
* *
* <pre> * <pre>
* HTTP/1.1 101 Switching Protocols * HTTP/1.1 101 Switching Protocols
* Upgrade: websocket * Upgrade: websocket
* Connection: Upgrade * Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat * Sec-WebSocket-Protocol: chat
* </pre> * </pre>
* *
* @param channel * @param channel
* Channel * Channel
* @param response * @param response
* HTTP response returned from the server for the request sent by beginOpeningHandshake00(). * HTTP response returned from the server for the request sent by beginOpeningHandshake00().
* @throws WebSocketHandshakeException * @throws WebSocketHandshakeException
*/ */
@Override @Override
public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException { public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols"); final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols");
if (!response.getStatus().equals(status)) { if (!response.getStatus().equals(status)) {
throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus()); throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
} }
String upgrade = response.getHeader(Names.UPGRADE); String upgrade = response.getHeader(Names.UPGRADE);
if (upgrade == null || !upgrade.equals(Values.WEBSOCKET.toLowerCase())) { if (upgrade == null || !upgrade.equals(Values.WEBSOCKET.toLowerCase())) {
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
+ response.getHeader(Names.UPGRADE)); + response.getHeader(Names.UPGRADE));
} }
String connection = response.getHeader(Names.CONNECTION); String connection = response.getHeader(Names.CONNECTION);
if (connection == null || !connection.equals(Values.UPGRADE)) { if (connection == null || !connection.equals(Values.UPGRADE)) {
throw new WebSocketHandshakeException("Invalid handshake response connection: " throw new WebSocketHandshakeException("Invalid handshake response connection: "
+ response.getHeader(Names.CONNECTION)); + response.getHeader(Names.CONNECTION));
} }
String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT); String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT);
if (accept == null || !accept.equals(this.expectedChallengeResponseString)) { if (accept == null || !accept.equals(this.expectedChallengeResponseString)) {
throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept,
this.expectedChallengeResponseString)); this.expectedChallengeResponseString));
} }
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder",
new WebSocket08FrameDecoder(false, this.allowExtensions)); new WebSocket08FrameDecoder(false, this.allowExtensions));
this.setOpenningHandshakeCompleted(true); this.setOpenningHandshakeCompleted(true);
} }
} }

View File

@ -42,150 +42,150 @@ import org.jboss.netty.util.CharsetUtil;
*/ */
public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker13.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker13.class);
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private String expectedChallengeResponseString = null; private String expectedChallengeResponseString = null;
private static final String protocol = null; private static final String protocol = null;
private boolean allowExtensions = false; private boolean allowExtensions = false;
/** /**
* Constructor specifying the destination web socket location and version to initiate * Constructor specifying the destination web socket location and version to initiate
* *
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "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 server * Version of web socket specification to use to connect to the 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 socket frame * Allow extensions to be used in the reserved bits of the web 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
*/ */
public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subProtocol, public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subProtocol,
boolean allowExtensions, Map<String, String> customHeaders) { boolean allowExtensions, Map<String, String> customHeaders) {
super(webSocketURL, version, subProtocol, customHeaders); super(webSocketURL, version, subProtocol, customHeaders);
this.allowExtensions = allowExtensions; this.allowExtensions = allowExtensions;
} }
/** /**
* /** * /**
* <p> * <p>
* Sends the opening request to the server: * Sends the opening request to the server:
* </p> * </p>
* *
* <pre> * <pre>
* GET /chat HTTP/1.1 * GET /chat HTTP/1.1
* Host: server.example.com * Host: server.example.com
* Upgrade: websocket * Upgrade: websocket
* Connection: Upgrade * Connection: Upgrade
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
* Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Origin: http://example.com
* Sec-WebSocket-Protocol: chat, superchat * Sec-WebSocket-Protocol: chat, superchat
* Sec-WebSocket-Version: 13 * Sec-WebSocket-Version: 13
* </pre> * </pre>
* *
* @param channel * @param channel
* Channel into which we can write our request * Channel into which we can write our request
*/ */
@Override @Override
public void performOpeningHandshake(Channel channel) { public void performOpeningHandshake(Channel channel) {
// Get path // Get path
URI wsURL = this.getWebSocketURL(); URI wsURL = this.getWebSocketURL();
String path = wsURL.getPath(); String path = wsURL.getPath();
if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) { if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
path = wsURL.getPath() + "?" + wsURL.getQuery(); path = wsURL.getPath() + "?" + wsURL.getQuery();
} }
// Get 16 bit nonce and base 64 encode it // Get 16 bit nonce and base 64 encode it
byte[] nonce = createRandomBytes(16); byte[] nonce = createRandomBytes(16);
String key = base64Encode(nonce); String key = base64Encode(nonce);
String acceptSeed = key + MAGIC_GUID; String acceptSeed = key + MAGIC_GUID;
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII)); byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
this.expectedChallengeResponseString = base64Encode(sha1); this.expectedChallengeResponseString = base64Encode(sha1);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("WS Version 13 Client Handshake key: %s. Expected response: %s.", key, logger.debug(String.format("WS Version 13 Client Handshake key: %s. Expected response: %s.", key,
this.expectedChallengeResponseString)); this.expectedChallengeResponseString));
} }
// Format request // Format request
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path); HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
request.addHeader(Names.UPGRADE, Values.WEBSOCKET.toLowerCase()); request.addHeader(Names.UPGRADE, Values.WEBSOCKET.toLowerCase());
request.addHeader(Names.CONNECTION, Values.UPGRADE); request.addHeader(Names.CONNECTION, Values.UPGRADE);
request.addHeader(Names.SEC_WEBSOCKET_KEY, key); request.addHeader(Names.SEC_WEBSOCKET_KEY, key);
request.addHeader(Names.HOST, wsURL.getHost()); request.addHeader(Names.HOST, wsURL.getHost());
request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost()); request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost());
if (protocol != null && !protocol.equals("")) { if (protocol != null && !protocol.equals("")) {
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol); request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
} }
request.addHeader(Names.SEC_WEBSOCKET_VERSION, "13"); request.addHeader(Names.SEC_WEBSOCKET_VERSION, "13");
if (customHeaders != null) { if (customHeaders != null) {
for (String header : customHeaders.keySet()) { for (String header : customHeaders.keySet()) {
request.addHeader(header, customHeaders.get(header)); request.addHeader(header, customHeaders.get(header));
} }
} }
channel.write(request); channel.write(request);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket13FrameEncoder(true)); channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket13FrameEncoder(true));
} }
/** /**
* <p> * <p>
* Process server response: * Process server response:
* </p> * </p>
* *
* <pre> * <pre>
* HTTP/1.1 101 Switching Protocols * HTTP/1.1 101 Switching Protocols
* Upgrade: websocket * Upgrade: websocket
* Connection: Upgrade * Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat * Sec-WebSocket-Protocol: chat
* </pre> * </pre>
* *
* @param channel * @param channel
* Channel * Channel
* @param response * @param response
* HTTP response returned from the server for the request sent by beginOpeningHandshake00(). * HTTP response returned from the server for the request sent by beginOpeningHandshake00().
* @throws WebSocketHandshakeException * @throws WebSocketHandshakeException
*/ */
@Override @Override
public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException { public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols"); final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols");
if (!response.getStatus().equals(status)) { if (!response.getStatus().equals(status)) {
throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus()); throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
} }
String upgrade = response.getHeader(Names.UPGRADE); String upgrade = response.getHeader(Names.UPGRADE);
if (upgrade == null || !upgrade.equals(Values.WEBSOCKET.toLowerCase())) { if (upgrade == null || !upgrade.equals(Values.WEBSOCKET.toLowerCase())) {
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
+ response.getHeader(Names.UPGRADE)); + response.getHeader(Names.UPGRADE));
} }
String connection = response.getHeader(Names.CONNECTION); String connection = response.getHeader(Names.CONNECTION);
if (connection == null || !connection.equals(Values.UPGRADE)) { if (connection == null || !connection.equals(Values.UPGRADE)) {
throw new WebSocketHandshakeException("Invalid handshake response connection: " throw new WebSocketHandshakeException("Invalid handshake response connection: "
+ response.getHeader(Names.CONNECTION)); + response.getHeader(Names.CONNECTION));
} }
String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT); String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT);
if (accept == null || !accept.equals(this.expectedChallengeResponseString)) { if (accept == null || !accept.equals(this.expectedChallengeResponseString)) {
throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept,
this.expectedChallengeResponseString)); this.expectedChallengeResponseString));
} }
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder",
new WebSocket13FrameDecoder(false, this.allowExtensions)); new WebSocket13FrameDecoder(false, this.allowExtensions));
this.setOpenningHandshakeCompleted(true); this.setOpenningHandshakeCompleted(true);
} }
} }

View File

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

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 first fragment (frame) may also be the * Flag to indicate if this frame is the final fragment in a message. 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 first fragment (frame) may also be the * Flag to indicate if this frame is the final fragment in a message. 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,157 +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 "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "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) { public WebSocketServerHandshaker(String webSocketURL, String subProtocols) {
this.webSocketURL = webSocketURL; this.webSocketURL = webSocketURL;
this.subProtocols = subProtocols; 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

@ -58,141 +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 "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "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 version 0</a> and lower. This standard * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">HyBi version 0</a> and lower. This standard
* is really a rehash of <a href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76" >hixie-76</a> and * is really a rehash of <a href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76" >hixie-76</a> and
* <a href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75" >hixie-75</a>. * <a href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75" >hixie-75</a>.
* </p> * </p>
* *
* <p> * <p>
* Browser request to the server: * Browser request to the server:
* </p> * </p>
* *
* <pre> * <pre>
* GET /demo HTTP/1.1 * GET /demo HTTP/1.1
* Upgrade: WebSocket * Upgrade: WebSocket
* Connection: Upgrade * Connection: Upgrade
* Host: example.com * Host: example.com
* Origin: http://example.com * Origin: http://example.com
* Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 * Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
* Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 * Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
* *
* ^n:ds[4U * ^n:ds[4U
* </pre> * </pre>
* *
* <p> * <p>
* Server response: * Server response:
* </p> * </p>
* *
* <pre> * <pre>
* HTTP/1.1 101 WebSocket Protocol Handshake * HTTP/1.1 101 WebSocket Protocol Handshake
* Upgrade: WebSocket * Upgrade: WebSocket
* Connection: Upgrade * Connection: Upgrade
* Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Origin: http://example.com
* Sec-WebSocket-Location: ws://example.com/demo * Sec-WebSocket-Location: ws://example.com/demo
* Sec-WebSocket-Protocol: sample * Sec-WebSocket-Protocol: sample
* *
* 8jKS'y:G*Co,Wxa- * 8jKS'y:G*Co,Wxa-
* </pre> * </pre>
* *
* @param channel * @param channel
* Channel * Channel
* @param req * @param req
* HTTP request * HTTP request
* @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException
*/ */
@Override @Override
public void performOpeningHandshake(Channel channel, HttpRequest req) { 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)) if (!Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION))
|| !WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) { || !WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) {
return; 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, HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101,
isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake")); isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake"));
res.addHeader(Names.UPGRADE, WEBSOCKET); res.addHeader(Names.UPGRADE, WEBSOCKET);
res.addHeader(CONNECTION, Values.UPGRADE); 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

@ -45,122 +45,122 @@ import org.jboss.netty.util.CharsetUtil;
*/ */
public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker { public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker08.class); private static final InternalLogger logger = InternalLoggerFactory.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";
private boolean allowExtensions = false; private boolean allowExtensions = false;
/** /**
* 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 "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "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 socket frame * Allow extensions to be used in the reserved bits of the web socket frame
*/ */
public WebSocketServerHandshaker08(String webSocketURL, String subProtocols, boolean allowExtensions) { public WebSocketServerHandshaker08(String webSocketURL, String subProtocols, boolean allowExtensions) {
super(webSocketURL, subProtocols); super(webSocketURL, subProtocols);
this.allowExtensions = allowExtensions; this.allowExtensions = allowExtensions;
} }
/** /**
* <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 version 8 to 10</a>. Version 8, 9 and * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-08">HyBi version 8 to 10</a>. Version 8, 9 and
* 10 share the same wire protocol. * 10 share the same wire protocol.
* </p> * </p>
* *
* <p> * <p>
* Browser request to the server: * Browser request to the server:
* </p> * </p>
* *
* <pre> * <pre>
* GET /chat HTTP/1.1 * GET /chat HTTP/1.1
* Host: server.example.com * Host: server.example.com
* Upgrade: websocket * Upgrade: websocket
* Connection: Upgrade * Connection: Upgrade
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
* Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Origin: http://example.com
* Sec-WebSocket-Protocol: chat, superchat * Sec-WebSocket-Protocol: chat, superchat
* Sec-WebSocket-Version: 8 * Sec-WebSocket-Version: 8
* </pre> * </pre>
* *
* <p> * <p>
* Server response: * Server response:
* </p> * </p>
* *
* <pre> * <pre>
* HTTP/1.1 101 Switching Protocols * HTTP/1.1 101 Switching Protocols
* Upgrade: websocket * Upgrade: websocket
* Connection: Upgrade * Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat * Sec-WebSocket-Protocol: chat
* </pre> * </pre>
* *
* @param channel * @param channel
* Channel * Channel
* @param req * @param req
* HTTP request * HTTP request
* @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException
*/ */
@Override @Override
public void performOpeningHandshake(Channel channel, HttpRequest req) { public void performOpeningHandshake(Channel channel, HttpRequest req) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Channel %s WS version 08 handshake", channel.getId())); logger.debug(String.format("Channel %s WS version 08 handshake", channel.getId()));
} }
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, "Switching Protocols")); HttpResponse res = new DefaultHttpResponse(HTTP_1_1, 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);
if (key == null) { if (key == null) {
res.setStatus(HttpResponseStatus.BAD_REQUEST); res.setStatus(HttpResponseStatus.BAD_REQUEST);
return; return;
} }
String acceptSeed = key + WEBSOCKET_08_ACCEPT_GUID; String acceptSeed = key + WEBSOCKET_08_ACCEPT_GUID;
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII)); byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
String accept = base64Encode(sha1); String accept = base64Encode(sha1);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("WS Version 08 Server Handshake key: %s. Response: %s.", key, accept)); logger.debug(String.format("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"));
res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase()); res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
res.addHeader(Names.CONNECTION, Names.UPGRADE); res.addHeader(Names.CONNECTION, Names.UPGRADE);
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, this.selectSubProtocol(protocol)); res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.selectSubProtocol(protocol));
} }
channel.write(res); channel.write(res);
// 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 WebSocket08FrameDecoder(true, this.allowExtensions)); p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket08FrameDecoder(true, this.allowExtensions));
p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket08FrameEncoder(false)); p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket08FrameEncoder(false));
} }
/** /**
* Echo back the closing frame and close the connection * Echo back the closing frame and close the connection
* *
* @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) {
ChannelFuture f = channel.write(frame); ChannelFuture f = channel.write(frame);
f.addListener(ChannelFutureListener.CLOSE); f.addListener(ChannelFutureListener.CLOSE);
} }
} }

View File

@ -45,122 +45,122 @@ import org.jboss.netty.util.CharsetUtil;
*/ */
public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker { public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker13.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker13.class);
public static final String WEBSOCKET_17_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; public static final String WEBSOCKET_13_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private boolean allowExtensions = false; private boolean allowExtensions = false;
/** /**
* 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 "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "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 socket frame * Allow extensions to be used in the reserved bits of the web socket frame
*/ */
public WebSocketServerHandshaker13(String webSocketURL, String subProtocols, boolean allowExtensions) { public WebSocketServerHandshaker13(String webSocketURL, String subProtocols, boolean allowExtensions) {
super(webSocketURL, subProtocols); super(webSocketURL, subProtocols);
this.allowExtensions = allowExtensions; this.allowExtensions = allowExtensions;
} }
/** /**
* <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 versions 13-17</a>. Versions 13-17 * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi versions 13-17</a>. Versions 13-17
* share the same wire protocol. * share the same wire protocol.
* </p> * </p>
* *
* <p> * <p>
* Browser request to the server: * Browser request to the server:
* </p> * </p>
* *
* <pre> * <pre>
* GET /chat HTTP/1.1 * GET /chat HTTP/1.1
* Host: server.example.com * Host: server.example.com
* Upgrade: websocket * Upgrade: websocket
* Connection: Upgrade * Connection: Upgrade
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
* Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Origin: http://example.com
* Sec-WebSocket-Protocol: chat, superchat * Sec-WebSocket-Protocol: chat, superchat
* Sec-WebSocket-Version: 13 * Sec-WebSocket-Version: 13
* </pre> * </pre>
* *
* <p> * <p>
* Server response: * Server response:
* </p> * </p>
* *
* <pre> * <pre>
* HTTP/1.1 101 Switching Protocols * HTTP/1.1 101 Switching Protocols
* Upgrade: websocket * Upgrade: websocket
* Connection: Upgrade * Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat * Sec-WebSocket-Protocol: chat
* </pre> * </pre>
* *
* @param channel * @param channel
* Channel * Channel
* @param req * @param req
* HTTP request * HTTP request
* @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException
*/ */
@Override @Override
public void performOpeningHandshake(Channel channel, HttpRequest req) { public void performOpeningHandshake(Channel channel, HttpRequest req) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Channel %s WS version 13 handshake", channel.getId())); logger.debug(String.format("Channel %s WS version 13 handshake", channel.getId()));
} }
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, "Switching Protocols")); HttpResponse res = new DefaultHttpResponse(HTTP_1_1, 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);
if (key == null) { if (key == null) {
res.setStatus(HttpResponseStatus.BAD_REQUEST); res.setStatus(HttpResponseStatus.BAD_REQUEST);
return; return;
} }
String acceptSeed = key + WEBSOCKET_17_ACCEPT_GUID; String acceptSeed = key + WEBSOCKET_13_ACCEPT_GUID;
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII)); byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
String accept = base64Encode(sha1); String accept = base64Encode(sha1);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("WS Version 13 Server Handshake key: %s. Response: %s.", key, accept)); logger.debug(String.format("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"));
res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase()); res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
res.addHeader(Names.CONNECTION, Names.UPGRADE); res.addHeader(Names.CONNECTION, Names.UPGRADE);
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, this.selectSubProtocol(protocol)); res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.selectSubProtocol(protocol));
} }
channel.write(res); channel.write(res);
// 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 WebSocket13FrameDecoder(true, this.allowExtensions)); p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket13FrameDecoder(true, this.allowExtensions));
p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false)); p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false));
} }
/** /**
* Echo back the closing frame and close the connection * Echo back the closing frame and close the connection
* *
* @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) {
ChannelFuture f = channel.write(frame); ChannelFuture f = channel.write(frame);
f.addListener(ChannelFutureListener.CLOSE); f.addListener(ChannelFutureListener.CLOSE);
} }
} }

View File

@ -24,72 +24,72 @@ import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names; import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
/** /**
* Instances the appropriate handshake class to use for clients * Instances the appropriate handshake class to use for servers
*/ */
public class WebSocketServerHandshakerFactory { public class WebSocketServerHandshakerFactory {
private final String webSocketURL; private final String webSocketURL;
private final String subProtocols; private final String subProtocols;
private boolean allowExtensions = false; private boolean allowExtensions = false;
/** /**
* 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 "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "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.
* @param allowExtensions * @param allowExtensions
* Allow extensions to be used in the reserved bits of the web socket frame * Allow extensions to be used in the reserved bits of the web socket frame
*/ */
public WebSocketServerHandshakerFactory(String webSocketURL, String subProtocols, boolean allowExtensions) { public WebSocketServerHandshakerFactory(String webSocketURL, String subProtocols, boolean allowExtensions) {
this.webSocketURL = webSocketURL; this.webSocketURL = webSocketURL;
this.subProtocols = subProtocols; this.subProtocols = subProtocols;
this.allowExtensions = allowExtensions; this.allowExtensions = allowExtensions;
} }
/** /**
* Instances a new handshaker * Instances a new handshaker
* *
* @return A new WebSocketServerHandshaker for the requested web socket version. Null if web socket version is not * @return A new WebSocketServerHandshaker for the requested web socket version. Null if web socket version is not
* supported. * supported.
*/ */
public WebSocketServerHandshaker newHandshaker(HttpRequest req) { public WebSocketServerHandshaker newHandshaker(HttpRequest req) {
String version = req.getHeader(Names.SEC_WEBSOCKET_VERSION); String version = req.getHeader(Names.SEC_WEBSOCKET_VERSION);
if (version != null) { if (version != null) {
if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) { if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) {
// Version 13 of the wire protocol - assume version 17 of the // Version 13 of the wire protocol - assume version 17 of the
// specification. // specification.
return new WebSocketServerHandshaker13(webSocketURL, subProtocols, this.allowExtensions); return new WebSocketServerHandshaker13(webSocketURL, subProtocols, this.allowExtensions);
} else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) { } else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) {
// Version 8 of the wire protocol - assume version 10 of the // Version 8 of the wire protocol - assume version 10 of the
// specification. // specification.
return new WebSocketServerHandshaker08(webSocketURL, subProtocols, this.allowExtensions); return new WebSocketServerHandshaker08(webSocketURL, subProtocols, this.allowExtensions);
} else { } else {
return null; return null;
} }
} else { } else {
// Assume version 00 where version header was not specified // Assume version 00 where version header was not specified
return new WebSocketServerHandshaker00(webSocketURL, subProtocols); return new WebSocketServerHandshaker00(webSocketURL, subProtocols);
} }
} }
/** /**
* 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
*/ */
public void sendUnsupportedWebSocketVersionResponse(Channel channel) { public void sendUnsupportedWebSocketVersionResponse(Channel channel) {
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(101, HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(101,
"Switching Protocols")); "Switching Protocols"));
res.setStatus(HttpResponseStatus.UPGRADE_REQUIRED); res.setStatus(HttpResponseStatus.UPGRADE_REQUIRED);
res.setHeader(Names.SEC_WEBSOCKET_VERSION, WebSocketVersion.V13.toHttpHeaderValue()); res.setHeader(Names.SEC_WEBSOCKET_VERSION, WebSocketVersion.V13.toHttpHeaderValue());
channel.write(res); channel.write(res);
} }
} }

View File

@ -21,35 +21,35 @@ package org.jboss.netty.handler.codec.http.websocketx;
* </p> * </p>
*/ */
public enum WebSocketVersion { public enum WebSocketVersion {
UNKNOWN, UNKNOWN,
/** /**
* <a href= "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00" * <a href= "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= "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10" * <a href= "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 originally <a href= * <a href="http://tools.ietf.org/html/rfc6455 ">RFC 6455</a>. This was originally <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17" >draft-ietf-hybi-thewebsocketprotocol- * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17" >draft-ietf-hybi-thewebsocketprotocol-
* 17</a> * 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) {
return "13"; return "13";
} }
throw new IllegalArgumentException(this.toString() + " cannot be converted to a string."); throw new IllegalArgumentException(this.toString() + " cannot be converted to a HttpHeaderValue.");
} }
} }