Change tabs to spaces. Damn you auto code format!
This commit is contained in:
parent
16bf84dac1
commit
073521d37d
@ -29,24 +29,24 @@ import io.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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,86 +48,86 @@ import io.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,14 +26,14 @@ import io.netty.handler.codec.http.HttpResponseEncoder;
|
|||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public class WebSocketServerPipelineFactory implements ChannelPipelineFactory {
|
public class WebSocketServerPipelineFactory implements ChannelPipelineFactory {
|
||||||
@Override
|
@Override
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>10. 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 io.netty.example.http.websocketx.autobahn;
|
package io.netty.example.http.websocketx.autobahn;
|
||||||
|
|
||||||
|
@ -35,98 +35,98 @@ import io.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();
|
||||||
|
|
||||||
// Connect with RFC 6455. You can change it to V10 or V00.
|
// Connect with RFC 6455. You can change it to V10 or V00.
|
||||||
// If you change it to V00, ping is not supported and remember to change HttpResponseDecoder to
|
// If you change it to V00, ping is not supported and remember to change HttpResponseDecoder to
|
||||||
// WebSocketHttpResponseDecoder in the pipeline.
|
// 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, null);
|
callbackHandler, null);
|
||||||
|
|
||||||
// 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 - only supported for V10 and up.
|
// Ping - only supported for V10 and up.
|
||||||
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() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,37 +31,37 @@ import io.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);
|
||||||
}
|
}
|
||||||
|
@ -29,26 +29,26 @@ import io.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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
* custom HTTP headers to pass during the handshake
|
* custom HTTP headers to pass during the handshake
|
||||||
* @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,
|
||||||
Map<String, String> customHeaders) {
|
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() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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 WebSocketHttpResponseDecoder instead for
|
// If you wish to support HyBi V00, you need to use 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,81 +49,81 @@ import io.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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChannelFuture connect() {
|
public ChannelFuture connect() {
|
||||||
return bootstrap.connect(new InetSocketAddress(url.getHost(), url.getPort()));
|
return bootstrap.connect(new InetSocketAddress(url.getHost(), url.getPort()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChannelFuture disconnect() {
|
public ChannelFuture disconnect() {
|
||||||
return channel.close();
|
return channel.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -25,27 +25,27 @@ import io.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,33 +36,30 @@ import io.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/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,99 +48,99 @@ import io.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,69 +24,69 @@ import io.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,14 +26,14 @@ import io.netty.handler.codec.http.HttpResponseEncoder;
|
|||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public class WebSocketServerPipelineFactory implements ChannelPipelineFactory {
|
public class WebSocketServerPipelineFactory implements ChannelPipelineFactory {
|
||||||
@Override
|
@Override
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,35 +46,35 @@ import io.netty.channel.socket.nio.NioServerSocketChannelFactory;
|
|||||||
* </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/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,99 +48,99 @@ import io.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,69 +24,69 @@ import io.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,19 +29,19 @@ import io.netty.handler.ssl.SslHandler;
|
|||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public class WebSocketSslServerPipelineFactory implements ChannelPipelineFactory {
|
public class WebSocketSslServerPipelineFactory implements ChannelPipelineFactory {
|
||||||
@Override
|
@Override
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,73 +30,73 @@ import io.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,42 +23,42 @@ import io.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() + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,28 +22,28 @@ import io.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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,114 +25,114 @@ import io.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,43 +23,43 @@ import io.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() + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,42 +23,42 @@ import io.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() + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,98 +24,98 @@ import io.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() + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,9 @@ package io.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,59 +23,59 @@ package io.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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,101 +33,101 @@ import io.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,65 +33,65 @@ import io.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,325 +55,325 @@ import io.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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,117 +57,117 @@ import io.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -43,16 +43,16 @@ package io.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,14 +45,14 @@ package io.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,177 +32,177 @@ import io.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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param webSocketURL
|
* @param webSocketURL
|
||||||
* @param version
|
* @param version
|
||||||
* @param subProtocol
|
* @param subProtocol
|
||||||
*/
|
*/
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,205 +45,205 @@ import io.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -42,151 +42,151 @@ import io.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -42,150 +42,150 @@ import io.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 to send during the handshake
|
* Custom HTTP headers to send during the handshake
|
||||||
* @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 " + version.toString() + " not supported.");
|
throw new WebSocketHandshakeException("Protocol version " + version.toString() + " not supported.");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,57 +22,57 @@ import io.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,5 +19,5 @@ package io.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
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,13 @@ package io.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,157 +30,157 @@ import io.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,141 +58,141 @@ import io.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 WS Version 00 server handshake", channel.getId()));
|
logger.debug(String.format("Channel %s WS Version 00 server 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,122 +45,122 @@ import io.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 8 server handshake", channel.getId()));
|
logger.debug(String.format("Channel %s WS Version 8 server 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 8 Server Handshake key: %s. Response: %s.", key, accept));
|
logger.debug(String.format("WS Version 8 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -46,122 +46,122 @@ import io.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 server handshake", channel.getId()));
|
logger.debug(String.format("Channel %s WS Version 13 server 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,70 +24,70 @@ import io.netty.handler.codec.http.HttpVersion;
|
|||||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
import io.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 - RFC 6455 (version 17 of the draft hybi specification).
|
// Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi 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 - version 10 of the draft hybi specification.
|
// Version 8 of the wire protocol - version 10 of the draft hybi 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,38 +25,38 @@ package io.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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Value for HTTP Header 'Sec-WebSocket-Version'
|
* @return Value for HTTP Header 'Sec-WebSocket-Version'
|
||||||
*/
|
*/
|
||||||
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() + " does not have a HttpHeaderValue.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user