Author: kpreisser Date: Tue Oct 8 16:01:28 2013 New Revision: 1530325 URL: http://svn.apache.org/r1530325 Log: Improve Drawboard Example: - Check if buffered messages exceed a specific size, to avoid a DoS. - Combine buffered string message to reduce TCP overhead when sending them.
Added: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/CloseWebsocketMessage.java (with props) Modified: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/Client.java tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/DrawboardEndpoint.java tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/Room.java Modified: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/Client.java URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/Client.java?rev=1530325&r1=1530324&r2=1530325&view=diff ============================================================================== --- tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/Client.java (original) +++ tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/Client.java Tue Oct 8 16:01:28 2013 @@ -16,22 +16,28 @@ */ package websocket.drawboard; +import java.io.IOException; import java.util.LinkedList; -import javax.websocket.RemoteEndpoint; +import javax.websocket.CloseReason; +import javax.websocket.CloseReason.CloseCodes; +import javax.websocket.RemoteEndpoint.Async; import javax.websocket.SendHandler; import javax.websocket.SendResult; +import javax.websocket.Session; import websocket.drawboard.wsmessages.AbstractWebsocketMessage; import websocket.drawboard.wsmessages.BinaryWebsocketMessage; +import websocket.drawboard.wsmessages.CloseWebsocketMessage; import websocket.drawboard.wsmessages.StringWebsocketMessage; /** - * Represents a client with methods to send messages. + * Represents a client with methods to send messages asynchronously. */ public class Client { - private final RemoteEndpoint.Async async; + private final Session session; + private final Async async; /** * Contains the messages wich are buffered until the previous @@ -43,11 +49,30 @@ public class Client { * If this client is currently sending a messages asynchronously. */ private volatile boolean isSendingMessage = false; + /** + * If this client is closing. If <code>true</code>, new messages to + * send will be ignored. + */ + private volatile boolean isClosing = false; + /** + * The length of all current buffered messages, to avoid iterating + * over a linked list. + */ + private volatile long messagesToSendLength = 0; - public Client(RemoteEndpoint.Async async) { - this.async = async; + public Client(Session session) { + this.session = session; + this.async = session.getAsyncRemote(); } + /** + * Asynchronously closes the Websocket session. This will wait until all + * remaining messages have been sent to the Client and then close + * the Websocket session. + */ + public void close() { + sendMessage(new CloseWebsocketMessage()); + } /** * Sends the given message asynchronously to the client. @@ -59,23 +84,71 @@ public class Client { */ public void sendMessage(AbstractWebsocketMessage msg) { synchronized (messagesToSend) { - if (isSendingMessage) { - // TODO: Check if the buffered messages exceed - // a specific amount - in that case, disconnect the client - // to prevent DoS. - - // TODO: Check if the last message is a - // String message - in that case we should concatenate them - // to reduce TCP overhead (using ";" as separator). - - messagesToSend.add(msg); - } else { - isSendingMessage = true; - internalSendMessageAsync(msg); + if (!isClosing) { + // Check if we have a Close message + if (msg instanceof CloseWebsocketMessage) { + isClosing = true; + } + + if (isSendingMessage) { + // Check if the buffered messages exceed + // a specific amount - in that case, disconnect the client + // to prevent DoS. + // In this case we check if there are >= 1000 messages + // or length(of all messages) >= 1000000 bytes. + if (messagesToSend.size() >= 1000 + || messagesToSendLength >= 1000000) { + isClosing = true; + + // Discard the new message and close the session immediately. + CloseReason cr = new CloseReason( + CloseCodes.VIOLATED_POLICY, + "Send Buffer exceeded"); + try { + session.close(cr); + } catch (IOException e) { + // Ignore + } + + } else { + + // Check if the last message and the new message are + // String messages - in that case we concatenate them + // to reduce TCP overhead (using ";" as separator). + if (msg instanceof StringWebsocketMessage + && !messagesToSend.isEmpty() + && messagesToSend.getLast() + instanceof StringWebsocketMessage) { + + StringWebsocketMessage ms = + (StringWebsocketMessage) messagesToSend.removeLast(); + messagesToSendLength -= calculateMessageLength(ms); + + String concatenated = ms.getString() + ";" + + ((StringWebsocketMessage) msg).getString(); + msg = new StringWebsocketMessage(concatenated); + } + + messagesToSend.add(msg); + messagesToSendLength += calculateMessageLength(msg); + } + } else { + isSendingMessage = true; + internalSendMessageAsync(msg); + } } + } + } + private long calculateMessageLength(AbstractWebsocketMessage msg) { + if (msg instanceof BinaryWebsocketMessage) { + return ((BinaryWebsocketMessage) msg).getBytes().capacity(); + } else if (msg instanceof StringWebsocketMessage) { + return ((StringWebsocketMessage) msg).getString().length() * 2; } + + return 0; } /** @@ -91,8 +164,12 @@ public class Client { } else if (msg instanceof BinaryWebsocketMessage) { BinaryWebsocketMessage bMsg = (BinaryWebsocketMessage) msg; async.sendBinary(bMsg.getBytes(), sendHandler); + + } else if (msg instanceof CloseWebsocketMessage) { + // Close the session. + session.close(); } - } catch (IllegalStateException ex) { + } catch (IllegalStateException|IOException ex) { // Trying to write to the client when the session has // already been closed. // Ignore @@ -111,6 +188,8 @@ public class Client { if (!messagesToSend.isEmpty()) { AbstractWebsocketMessage msg = messagesToSend.remove(); + messagesToSendLength -= calculateMessageLength(msg); + internalSendMessageAsync(msg); } else { Modified: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/DrawboardEndpoint.java URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/DrawboardEndpoint.java?rev=1530325&r1=1530324&r2=1530325&view=diff ============================================================================== --- tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/DrawboardEndpoint.java (original) +++ tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/DrawboardEndpoint.java Tue Oct 8 16:01:28 2013 @@ -70,7 +70,7 @@ public final class DrawboardEndpoint ext // Set maximum messages size to 10.000 bytes. session.setMaxTextMessageBufferSize(10000); session.addMessageHandler(stringHandler); - final Client client = new Client(session.getAsyncRemote()); + final Client client = new Client(session); room.invoke(new Runnable() { @Override Modified: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/Room.java URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/Room.java?rev=1530325&r1=1530324&r2=1530325&view=diff ============================================================================== --- tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/Room.java (original) +++ tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/Room.java Tue Oct 8 16:01:28 2013 @@ -389,8 +389,10 @@ public final class Room { * the client disconnects. */ public void removeFromRoom() { - room.internalRemovePlayer(this); - room = null; + if (room != null) { + room.internalRemovePlayer(this); + room = null; + } } Added: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/CloseWebsocketMessage.java URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/CloseWebsocketMessage.java?rev=1530325&view=auto ============================================================================== --- tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/CloseWebsocketMessage.java (added) +++ tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/CloseWebsocketMessage.java Tue Oct 8 16:01:28 2013 @@ -0,0 +1,8 @@ +package websocket.drawboard.wsmessages; + +/** + * Represents a "close" message that closes the session. + */ +public class CloseWebsocketMessage extends AbstractWebsocketMessage { + +} Propchange: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard/wsmessages/CloseWebsocketMessage.java ------------------------------------------------------------------------------ svn:eol-style = native --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org