You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by kp...@apache.org on 2013/10/08 18:01:28 UTC

svn commit: r1530325 - in /tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/drawboard: Client.java DrawboardEndpoint.java Room.java wsmessages/CloseWebsocketMessage.java

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-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org