You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2012/03/16 00:39:49 UTC

svn commit: r1301270 [1/2] - in /tomcat/tc7.0.x/trunk: ./ java/org/apache/catalina/websocket/ java/org/apache/coyote/ java/org/apache/coyote/ajp/ java/org/apache/coyote/http11/ java/org/apache/coyote/http11/upgrade/ test/org/apache/catalina/websocket/ ...

Author: markt
Date: Thu Mar 15 23:39:48 2012
New Revision: 1301270

URL: http://svn.apache.org/viewvc?rev=1301270&view=rev
Log:
More WebSocket

Added:
    tomcat/tc7.0.x/trunk/test/org/apache/catalina/websocket/TestUtf8.java
      - copied unchanged from r1295997, tomcat/trunk/test/org/apache/catalina/websocket/TestUtf8.java
    tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/
      - copied from r1297716, tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/
    tomcat/tc7.0.x/trunk/webapps/examples/websocket/snake.html   (contents, props changed)
      - copied, changed from r1297716, tomcat/trunk/webapps/examples/websocket/snake.html
Modified:
    tomcat/tc7.0.x/trunk/   (props changed)
    tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/Constants.java
    tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/LocalStrings.properties
    tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java
    tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsFrame.java
    tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsInputStream.java
    tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java
    tomcat/tc7.0.x/trunk/java/org/apache/coyote/AbstractProtocol.java
    tomcat/tc7.0.x/trunk/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
    tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11AprProtocol.java
    tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11NioProtocol.java
    tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11Protocol.java
    tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/LocalStrings.properties
    tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java
    tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java
    tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeInbound.java
    tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java
    tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
    tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java
    tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoMessage.java
    tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Direction.java   (contents, props changed)
    tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Location.java   (contents, props changed)
    tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java   (contents, props changed)
    tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java   (contents, props changed)
    tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/web.xml
    tomcat/tc7.0.x/trunk/webapps/examples/websocket/echo.html
    tomcat/tc7.0.x/trunk/webapps/examples/websocket/index.html

Propchange: tomcat/tc7.0.x/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Thu Mar 15 23:39:48 2012
@@ -1 +1 @@
-/tomcat/trunk:1156115-1157160,1157162-1157859,1157862-1157942,1157945-1160347,1160349-1163716,1163718-1166689,1166691-1174340,1174342-1175596,1175598-1175611,1175613-1175932,1175934-1177783,1177785-1177980,1178006-1180720,1180722-1183094,1183096-1187753,1187755,1187775,1187801,1187806,1187809,1187826-1188312,1188314-1188401,1188646-1188840,1188842-1190176,1190178-1195223,1195225-1195953,1195955,1195957-1201238,1201240-1203345,1203347-1206623,1206625-1208046,1208073,1208096,1208114,1208145,1208772,1209194-1212125,1212127-1220291,1220293,1220295-1221321,1221323-1222328,1222332-1222401,1222405-1222795,1222850-1222950,1222969-1225326,1225328-1225463,1225465,1225627,1225629-1226534,1226536-1228908,1228911-1228923,1228927-1229532,1229534-1230766,1230768-1231625,1231627-1233414,1233419-1235207,1235209-1237425,1237427,1237429-1237977,1237981,1237985,1237995,1238070,1238073,1239024-1239048,1239050-1239062,1239135,1239256,1239258-1239485,1239785-1240046,1240101,1240106,1240109,1240112
 ,1240114,1240116,1240118,1240121,1240329,1240474-1240850,1240857,1241087,1241160,1241408-1241822,1241908-1241909,1241912-1242110,1242371-1292130,1292134-1292458,1292464-1292670,1292672-1292776,1292780-1293392,1293831-1293832,1295998,1296284,1297014-1297015,1297017,1297158,1297177,1297202,1297209,1297213,1297717,1297722,1297729,1297768,1297778,1297818,1297828,1297979,1297987,1298121,1298140,1298590,1298592,1298628-1298629,1298794,1298983-1298984,1299020,1299034,1299819,1300154-1300155,1300569,1300948
+/tomcat/trunk:1156115-1157160,1157162-1157859,1157862-1157942,1157945-1160347,1160349-1163716,1163718-1166689,1166691-1174340,1174342-1175596,1175598-1175611,1175613-1175932,1175934-1177783,1177785-1177980,1178006-1180720,1180722-1183094,1183096-1187753,1187755,1187775,1187801,1187806,1187809,1187826-1188312,1188314-1188401,1188646-1188840,1188842-1190176,1190178-1195223,1195225-1195953,1195955,1195957-1201238,1201240-1203345,1203347-1206623,1206625-1208046,1208073,1208096,1208114,1208145,1208772,1209194-1212125,1212127-1220291,1220293,1220295-1221321,1221323-1222328,1222332-1222401,1222405-1222795,1222850-1222950,1222969-1225326,1225328-1225463,1225465,1225627,1225629-1226534,1226536-1228908,1228911-1228923,1228927-1229532,1229534-1230766,1230768-1231625,1231627-1233414,1233419-1235207,1235209-1237425,1237427,1237429-1237977,1237981,1237985,1237995,1238070,1238073,1239024-1239048,1239050-1239062,1239135,1239256,1239258-1239485,1239785-1240046,1240101,1240106,1240109,1240112
 ,1240114,1240116,1240118,1240121,1240329,1240474-1240850,1240857,1241087,1241160,1241408-1241822,1241908-1241909,1241912-1242110,1242371-1292130,1292134-1292458,1292464-1292670,1292672-1292776,1292780-1293392,1293397-1297017,1297019-1297963,1297965-1299820,1300154-1300155,1300569,1300948

Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/Constants.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/Constants.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/Constants.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/Constants.java Thu Mar 15 23:39:48 2012
@@ -29,4 +29,93 @@ public class Constants {
     public static final byte OPCODE_CLOSE = 0x08;
     public static final byte OPCODE_PING = 0x09;
     public static final byte OPCODE_PONG = 0x0A;
+
+    // Status Codes
+    // Definitions as per RFC 6455 (http://tools.ietf.org/html/rfc6455)
+    /**
+     * 1000 indicates a normal closure, meaning whatever purpose the
+     * connection was established for has been fulfilled.
+     */
+    public static final int STATUS_CLOSE_NORMAL = 1000;
+
+    /**
+     * 1001 indicates that an endpoint is "going away", such as a server
+     * going down, or a browser having navigated away from a page.
+     */
+    public static final int STATUS_SHUTDOWN = 1001;
+
+    /**
+     * 1002 indicates that an endpoint is terminating the connection due
+     * to a protocol error.
+     */
+    public static final int STATUS_PROTOCOL_ERROR = 1002;
+
+    /**
+     * 1003 indicates that an endpoint is terminating the connection
+     * because it has received a type of data it cannot accept (e.g. an
+     * endpoint that understands only text data MAY send this if it
+     * receives a binary message).
+     */
+    public static final int STATUS_UNEXPECTED_DATA_TYPE = 1003;
+
+    // 1004 is reserved. The specific meaning might be defined in the future.
+
+    /**
+     * 1005 is a reserved value and MUST NOT be set as a status code in a
+     * Close control frame by an endpoint.  It is designated for use in
+     * applications expecting a status code to indicate that no status
+     * code was actually present.
+     */
+    public static final int STATUS_CODE_MISSING = 1005;
+
+    /**
+     * 1006 is a reserved value and MUST NOT be set as a status code in a
+     * Close control frame by an endpoint.  It is designated for use in
+     * applications expecting a status code to indicate that the
+     * connection was closed abnormally, e.g. without sending or
+     * receiving a Close control frame.
+     */
+    public static final int STATUS_CLOSED_UNEXPECTEDLY = 1006;
+
+    /**
+     * 1007 indicates that an endpoint is terminating the connection
+     * because it has received data within a message that was not
+     * consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
+     * data within a text message).
+     */
+    public static final int STATUS_BAD_DATA = 1007;
+
+    /**
+     * 1008 indicates that an endpoint is terminating the connection
+     * because it has received a message that violates its policy.  This
+     * is a generic status code that can be returned when there is no
+     * other more suitable status code (e.g. 1003 or 1009), or if there
+     * is a need to hide specific details about the policy.
+     */
+    public static final int STATUS_POLICY_VIOLATION = 1008;
+
+    /**
+     * 1009 indicates that an endpoint is terminating the connection
+     * because it has received a message which is too big for it to
+     * process.
+     */
+    public static final int STATUS_MESSAGE_TOO_LARGE = 1009;
+
+    /**
+     * 1010 indicates that an endpoint (client) is terminating the
+     * connection because it has expected the server to negotiate one or
+     * more extension, but the server didn't return them in the response
+     * message of the WebSocket handshake.  The list of extensions which
+     * are needed SHOULD appear in the /reason/ part of the Close frame.
+     * Note that this status code is not used by the server, because it
+     * can fail the WebSocket handshake instead.
+     */
+    public static final int STATUS_REQUIRED_EXTENSION = 1010;
+
+    /**
+     * 1011 indicates that a server is terminating the connection because it
+     * encountered an unexpected condition that prevented it from fulfilling the
+     * request.
+     */
+    public static final int STATUS_UNEXPECTED_CONDITION = 1011;
 }

Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/LocalStrings.properties?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/LocalStrings.properties (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/LocalStrings.properties Thu Mar 15 23:39:48 2012
@@ -15,9 +15,11 @@
 
 frame.eos=The end of the stream was reached before the expected number of payload bytes could be read
 frame.invalidUtf8=A sequence of bytes was received that did not represent valid UTF-8
+frame.readFailed=Failed to read the first byte of the next WebSocket frame. The return value from the read was [{0}]
+frame.readEos=The end of the stream was reached when trying to read the first byte of a new WebSocket frame
 frame.notMasked=The client frame was not masked but all client frames must be masked
 
-is.notContinutation=A frame with the OpCode [{0}] was recieved when a continuation frame was expected
+is.notContinuation=A frame with the OpCode [{0}] was received when a continuation frame was expected
 is.unknownOpCode=A frame with the unrecognized OpCode [{0}] was received
 
 message.bufferTooSmall=The buffer is not big enough to contain the message currently being processed

Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java Thu Mar 15 23:39:48 2012
@@ -20,6 +20,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
+import java.nio.ByteBuffer;
 import java.nio.charset.MalformedInputException;
 import java.nio.charset.UnmappableCharacterException;
 
@@ -38,10 +39,51 @@ public abstract class StreamInbound impl
 
     private UpgradeProcessor<?> processor = null;
     private WsOutbound outbound;
+    private int outboundByteBufferSize = WsOutbound.DEFAULT_BUFFER_SIZE;
+    private int outboundCharBufferSize = WsOutbound.DEFAULT_BUFFER_SIZE;
+
+
+
+    public int getOutboundByteBufferSize() {
+        return outboundByteBufferSize;
+    }
+
+
+    /**
+     * This only applies to the {@link WsOutbound} instance returned from
+     * {@link #getWsOutbound()} created by a subsequent call to
+     * {@link #setUpgradeOutbound(UpgradeOutbound)}. The current
+     * {@link WsOutbound} instance, if any, is not affected.
+     *
+     * @param outboundByteBufferSize
+     */
+    public void setOutboundByteBufferSize(int outboundByteBufferSize) {
+        this.outboundByteBufferSize = outboundByteBufferSize;
+    }
+
+
+    public int getOutboundCharBufferSize() {
+        return outboundCharBufferSize;
+    }
+
+
+    /**
+     * This only applies to the {@link WsOutbound} instance returned from
+     * {@link #getWsOutbound()} created by a subsequent call to
+     * {@link #setUpgradeOutbound(UpgradeOutbound)}. The current
+     * {@link WsOutbound} instance, if any, is not affected.
+     *
+     * @param outboundCharBufferSize
+     */
+    public void setOutboundCharBufferSize(int outboundCharBufferSize) {
+        this.outboundCharBufferSize = outboundCharBufferSize;
+    }
+
 
     @Override
     public final void setUpgradeOutbound(UpgradeOutbound upgradeOutbound) {
-        outbound = new WsOutbound(upgradeOutbound);
+        outbound = new WsOutbound(upgradeOutbound, outboundByteBufferSize,
+                outboundCharBufferSize);
     }
 
 
@@ -62,59 +104,102 @@ public abstract class StreamInbound impl
 
     @Override
     public final SocketState onData() throws IOException {
-        // Must be start the start of a frame or series of frames
+        // Must be start the start of a message (which may consist of multiple
+        // frames)
+        WsInputStream wsIs = new WsInputStream(processor, getWsOutbound());
 
         try {
-            WsInputStream wsIs = new WsInputStream(processor, getWsOutbound());
-
-            WsFrame frame = wsIs.getFrame();
-
-            // TODO User defined extensions may define values for rsv
-            if (frame.getRsv() > 0) {
-                getWsOutbound().close(1002, null);
-                return SocketState.CLOSED;
-            }
-
-            byte opCode = frame.getOpCode();
+            WsFrame frame = wsIs.nextFrame(true);
 
-            if (opCode == Constants.OPCODE_BINARY) {
-                onBinaryData(wsIs);
-                return SocketState.UPGRADED;
-            } else if (opCode == Constants.OPCODE_TEXT) {
-                InputStreamReader r =
-                        new InputStreamReader(wsIs, new Utf8Decoder());
-                onTextData(r);
-                return SocketState.UPGRADED;
+            while (frame != null) {
+                // TODO User defined extensions may define values for rsv
+                if (frame.getRsv() > 0) {
+                    closeOutboundConnection(
+                            Constants.STATUS_PROTOCOL_ERROR, null);
+                    return SocketState.CLOSED;
+                }
+
+                byte opCode = frame.getOpCode();
+
+                if (opCode == Constants.OPCODE_BINARY) {
+                    onBinaryData(wsIs);
+                } else if (opCode == Constants.OPCODE_TEXT) {
+                    InputStreamReader r =
+                            new InputStreamReader(wsIs, new Utf8Decoder());
+                    onTextData(r);
+                } else if (opCode == Constants.OPCODE_CLOSE){
+                    closeOutboundConnection(frame);
+                    return SocketState.CLOSED;
+                } else if (opCode == Constants.OPCODE_PING) {
+                    getWsOutbound().pong(frame.getPayLoad());
+                } else if (opCode == Constants.OPCODE_PONG) {
+                    // NO-OP
+                } else {
+                    // Unknown OpCode
+                    closeOutboundConnection(
+                            Constants.STATUS_PROTOCOL_ERROR, null);
+                    return SocketState.CLOSED;
+                }
+                frame = wsIs.nextFrame(false);
             }
-
-            if (opCode == Constants.OPCODE_CLOSE){
-                getWsOutbound().close(frame);
-                return SocketState.CLOSED;
-            } else if (opCode == Constants.OPCODE_PING) {
-                getWsOutbound().pong(frame.getPayLoad());
-                return SocketState.UPGRADED;
-            } else if (opCode == Constants.OPCODE_PONG) {
-                // NO-OP
-                return SocketState.UPGRADED;
-            }
-
-            // Unknown OpCode
-            getWsOutbound().close(1002, null);
-            return SocketState.CLOSED;
         } catch (MalformedInputException mie) {
             // Invalid UTF-8
-            getWsOutbound().close(1007, null);
+            closeOutboundConnection(Constants.STATUS_BAD_DATA, null);
             return SocketState.CLOSED;
         } catch (UnmappableCharacterException uce) {
             // Invalid UTF-8
-            getWsOutbound().close(1007, null);
+            closeOutboundConnection(Constants.STATUS_BAD_DATA, null);
             return SocketState.CLOSED;
         } catch (IOException ioe) {
-            // Given something must have gone to reach this point, this might
-            // not work but try it anyway.
-            getWsOutbound().close(1002, null);
+            // Given something must have gone to reach this point, this
+            // might not work but try it anyway.
+            closeOutboundConnection(Constants.STATUS_PROTOCOL_ERROR, null);
             return SocketState.CLOSED;
         }
+        return SocketState.UPGRADED;
+    }
+
+    private void closeOutboundConnection(int status, ByteBuffer data) throws IOException {
+        try {
+            getWsOutbound().close(status, data);
+        } finally {
+            onClose(status);
+        }
+    }
+
+    private void closeOutboundConnection(WsFrame frame) throws IOException {
+        try {
+            getWsOutbound().close(frame);
+        } finally {
+            onClose(Constants.OPCODE_CLOSE);
+        }
+    }
+
+    @Override
+    public void onUpgradeComplete() {
+        onOpen(outbound);
+    }
+
+    /**
+     * Intended to be overridden by sub-classes that wish to be notified
+     * when the outbound connection is established. The default implementation
+     * is a NO-OP.
+     *
+     * @param outbound    The outbound WebSocket connection.
+     */
+    protected void onOpen(WsOutbound outbound) {
+        // NO-OP
+    }
+
+    /**
+     * Intended to be overridden by sub-classes that wish to be notified
+     * when the outbound connection is closed. The default implementation
+     * is a NO-OP.
+     *
+     * @param status    The status code of the close reason.
+     */
+    protected void onClose(int status) {
+        // NO-OP
     }
 
 

Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsFrame.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsFrame.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsFrame.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsFrame.java Thu Mar 15 23:39:48 2012
@@ -16,6 +16,7 @@
  */
 package org.apache.catalina.websocket;
 
+import java.io.EOFException;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
@@ -46,6 +47,7 @@ public class WsFrame {
      * Create the new WebSocket frame, reading data from the processor as
      * necessary.
      *
+     * @param first     First byte of data for this frame
      * @param processor Processor associated with the WebSocket connection on
      *                  which the frame has been sent
      *
@@ -53,14 +55,15 @@ public class WsFrame {
      *                      exception will trigger the closing of the WebSocket
      *                      connection.
      */
-    public WsFrame(UpgradeProcessor<?> processor) throws IOException {
+    private WsFrame(byte first,
+            UpgradeProcessor<?> processor) throws IOException {
 
-        int b = processorRead(processor);
+        int b = first & 0xFF;
         fin = (b & 0x80) > 0;
         rsv = (b & 0x70) >>> 4;
         opCode = (byte) (b & 0x0F);
 
-        b = processorRead(processor);
+        b = blockingRead(processor);
         // Client data must be masked
         if ((b & 0x80) == 0) {
             throw new IOException(sm.getString("frame.notMasked"));
@@ -69,11 +72,11 @@ public class WsFrame {
         payloadLength = b & 0x7F;
         if (payloadLength == 126) {
             byte[] extended = new byte[2];
-            processorRead(processor, extended);
+            blockingRead(processor, extended);
             payloadLength = Conversions.byteArrayToLong(extended);
         } else if (payloadLength == 127) {
             byte[] extended = new byte[8];
-            processorRead(processor, extended);
+            blockingRead(processor, extended);
             payloadLength = Conversions.byteArrayToLong(extended);
         }
 
@@ -86,12 +89,12 @@ public class WsFrame {
             }
         }
 
-        processorRead(processor, mask);
+        blockingRead(processor, mask);
 
         if (isControl()) {
             // Note: Payload limited to <= 125 bytes by test above
             payload = ByteBuffer.allocate((int) payloadLength);
-            processorRead(processor, payload);
+            blockingRead(processor, payload);
 
             if (opCode == Constants.OPCODE_CLOSE && payloadLength > 2) {
                 // Check close payload - if present - is valid UTF-8
@@ -138,9 +141,10 @@ public class WsFrame {
     }
 
 
-    // ----------------------------------- Guaranteed read methods for processor
-
-    private int processorRead(UpgradeProcessor<?> processor)
+    /*
+     * Blocks until a aingle byte has been read
+     */
+    private int blockingRead(UpgradeProcessor<?> processor)
             throws IOException {
         int result = processor.read();
         if (result == -1) {
@@ -150,12 +154,15 @@ public class WsFrame {
     }
 
 
-    private void processorRead(UpgradeProcessor<?> processor, byte[] bytes)
+    /*
+     * Blocks until the byte array has been filled.
+     */
+    private void blockingRead(UpgradeProcessor<?> processor, byte[] bytes)
             throws IOException {
         int read = 0;
         int last = 0;
         while (read < bytes.length) {
-            last = processor.read(bytes, read, bytes.length - read);
+            last = processor.read(true, bytes, read, bytes.length - read);
             if (last == -1) {
                 throw new IOException(sm.getString("frame.eos"));
             }
@@ -165,9 +172,10 @@ public class WsFrame {
 
 
     /*
-     * Intended to read whole payload. Therefore able to unmask.
+     * Intended to read whole payload and blocks until it has. Therefore able to
+     * unmask the payload data.
      */
-    private void processorRead(UpgradeProcessor<?> processor, ByteBuffer bb)
+    private void blockingRead(UpgradeProcessor<?> processor, ByteBuffer bb)
             throws IOException {
         int last = 0;
         while (bb.hasRemaining()) {
@@ -179,4 +187,39 @@ public class WsFrame {
         }
         bb.flip();
     }
+
+
+    /**
+     * Read the next WebSocket frame, reading data from the processor as
+     * necessary.
+     *
+     * @param processor Processor associated with the WebSocket connection on
+     *                  which the frame has been sent
+     *
+     * @param block Should this method block until a frame is presented if no
+     *              data is currently available to process. Note that is a
+     *              single byte is available, this method will block until the
+     *              complete frame (excluding payload for non-control frames) is
+     *              available.
+     *
+     * @throws IOException  If a problem occurs processing the frame. Any
+     *                      exception will trigger the closing of the WebSocket
+     *                      connection.
+     */
+    public static WsFrame nextFrame(UpgradeProcessor<?> processor,
+            boolean block) throws IOException {
+
+        byte[] first = new byte[1];
+        int read = processor.read(block, first, 0, 1);
+        if (read == 1) {
+            return new WsFrame(first[0], processor);
+        } else if (read == 0) {
+            return null;
+        } else if (read == -1) {
+            throw new EOFException(sm.getString("frame.readEos"));
+        } else {
+            throw new IOException(
+                    sm.getString("frame.readFailed", Integer.valueOf(read)));
+        }
+    }
 }

Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsInputStream.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsInputStream.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsInputStream.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsInputStream.java Thu Mar 15 23:39:48 2012
@@ -17,6 +17,7 @@
 package org.apache.catalina.websocket;
 
 import java.io.IOException;
+import java.io.InputStream;
 
 import org.apache.coyote.http11.upgrade.UpgradeProcessor;
 import org.apache.tomcat.util.res.StringManager;
@@ -27,7 +28,7 @@ import org.apache.tomcat.util.res.String
  * makes the number of bytes declared in the payload length available for
  * reading even if more bytes are available from the socket.
  */
-public class WsInputStream extends java.io.InputStream {
+public class WsInputStream extends InputStream {
 
     private static final StringManager sm =
             StringManager.getManager(Constants.Package);
@@ -43,55 +44,42 @@ public class WsInputStream extends java.
     private String error = null;
 
 
-    public WsInputStream(UpgradeProcessor<?> processor, WsOutbound outbound)
-            throws IOException {
+    public WsInputStream(UpgradeProcessor<?> processor, WsOutbound outbound) {
         this.processor = processor;
         this.outbound = outbound;
-        processFrame();
     }
 
 
-    public WsFrame getFrame() {
+    /**
+     * Process the next WebSocket frame.
+     *
+     * @param block Should this method block until a frame is presented if no
+     *              data is currently available to process. Note that is a
+     *              single byte is available, this method will block until the
+     *              complete frame (excluding payload for non-control frames) is
+     *              available.
+     *
+     * @return  The next frame to be processed or <code>null</code> if block is
+     *          <code>false</code> and there is no data to be processed.
+     *
+     * @throws IOException  If a problem occurs reading the data for the frame.
+     */
+    public WsFrame nextFrame(boolean block) throws IOException {
+        frame = WsFrame.nextFrame(processor, block);
+        if (frame != null) {
+            readThisFragment = 0;
+            remaining = frame.getPayLoadLength();
+        }
         return frame;
     }
 
 
-    private void processFrame() throws IOException {
-        frame = new WsFrame(processor);
-        readThisFragment = 0;
-        remaining = frame.getPayLoadLength();
-    }
-
-
     // ----------------------------------------------------- InputStream methods
 
     @Override
     public int read() throws IOException {
-        if (error != null) {
-            throw new IOException(error);
-        }
-        while (remaining == 0 && !getFrame().getFin()) {
-            // Need more data - process next frame
-            processFrame();
-            while (frame.isControl()) {
-                if (getFrame().getOpCode() == Constants.OPCODE_PING) {
-                    outbound.pong(frame.getPayLoad());
-                } else if (getFrame().getOpCode() == Constants.OPCODE_PONG) {
-                    // NO-OP. Swallow it.
-                } else if (getFrame().getOpCode() == Constants.OPCODE_CLOSE) {
-                    outbound.close(frame);
-                } else{
-                    throw new IOException(sm.getString("is.unknownOpCode",
-                            Byte.valueOf(getFrame().getOpCode())));
-                }
-                processFrame();
-            }
-            if (getFrame().getOpCode() != Constants.OPCODE_CONTINUATION) {
-                error = sm.getString("is.notContinutation",
-                        Byte.valueOf(getFrame().getOpCode()));
-                throw new IOException(error);
-            }
-        }
+
+        makePayloadDataAvailable();
 
         if (remaining == 0) {
             return -1;
@@ -111,31 +99,8 @@ public class WsInputStream extends java.
 
     @Override
     public int read(byte b[], int off, int len) throws IOException {
-        if (error != null) {
-            throw new IOException(error);
-        }
-        while (remaining == 0 && !getFrame().getFin()) {
-            // Need more data - process next frame
-            processFrame();
-            while (frame.isControl()) {
-                if (getFrame().getOpCode() == Constants.OPCODE_PING) {
-                    outbound.pong(frame.getPayLoad());
-                } else if (getFrame().getOpCode() == Constants.OPCODE_PONG) {
-                    // NO-OP. Swallow it.
-                } else if (getFrame().getOpCode() == Constants.OPCODE_CLOSE) {
-                    outbound.close(frame);
-                } else{
-                    throw new IOException(sm.getString("is.unknownOpCode",
-                            Byte.valueOf(getFrame().getOpCode())));
-                }
-                processFrame();
-            }
-            if (getFrame().getOpCode() != Constants.OPCODE_CONTINUATION) {
-                error = sm.getString("is.notContinutation",
-                        Byte.valueOf(getFrame().getOpCode()));
-                throw new IOException(error);
-            }
-        }
+
+        makePayloadDataAvailable();
 
         if (remaining == 0) {
             return -1;
@@ -144,7 +109,7 @@ public class WsInputStream extends java.
         if (len > remaining) {
             len = (int) remaining;
         }
-        int result = processor.read(b, off, len);
+        int result = processor.read(true, b, off, len);
         if(result == -1) {
             return -1;
         }
@@ -158,4 +123,35 @@ public class WsInputStream extends java.
         return result;
     }
 
+
+    /*
+     * Ensures that there is payload data ready to read.
+     */
+    private void makePayloadDataAvailable() throws IOException {
+        if (error != null) {
+            throw new IOException(error);
+        }
+        while (remaining == 0 && !frame.getFin()) {
+            // Need more data - process next frame
+            nextFrame(true);
+            while (frame.isControl()) {
+                if (frame.getOpCode() == Constants.OPCODE_PING) {
+                    outbound.pong(frame.getPayLoad());
+                } else if (frame.getOpCode() == Constants.OPCODE_PONG) {
+                    // NO-OP. Swallow it.
+                } else if (frame.getOpCode() == Constants.OPCODE_CLOSE) {
+                    outbound.close(frame);
+                } else{
+                    throw new IOException(sm.getString("is.unknownOpCode",
+                            Byte.valueOf(frame.getOpCode())));
+                }
+                nextFrame(true);
+            }
+            if (frame.getOpCode() != Constants.OPCODE_CONTINUATION) {
+                error = sm.getString("is.notContinuation",
+                        Byte.valueOf(frame.getOpCode()));
+                throw new IOException(error);
+            }
+        }
+    }
 }

Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java Thu Mar 15 23:39:48 2012
@@ -27,13 +27,16 @@ import org.apache.tomcat.util.buf.B2CCon
 import org.apache.tomcat.util.res.StringManager;
 
 /**
- * Provides the means to write WebSocket messages to the client.
+ * Provides the means to write WebSocket messages to the client. All methods
+ * that write to the client (or update a buffer that is later written to the
+ * client) are synchronized to prevent multiple threads trying to write to the
+ * client at the same time.
  */
 public class WsOutbound {
 
     private static final StringManager sm =
             StringManager.getManager(Constants.Package);
-    private static final int DEFAULT_BUFFER_SIZE = 8192;
+    public static final int DEFAULT_BUFFER_SIZE = 8192;
 
     private UpgradeOutbound upgradeOutbound;
     private ByteBuffer bb;
@@ -44,10 +47,15 @@ public class WsOutbound {
 
 
     public WsOutbound(UpgradeOutbound upgradeOutbound) {
+        this(upgradeOutbound, DEFAULT_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
+    }
+
+
+    public WsOutbound(UpgradeOutbound upgradeOutbound, int byteBufferSize,
+            int charBufferSize) {
         this.upgradeOutbound = upgradeOutbound;
-        // TODO: Make buffer size configurable
-        this.bb = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
-        this.cb = CharBuffer.allocate(DEFAULT_BUFFER_SIZE);
+        this.bb = ByteBuffer.allocate(byteBufferSize);
+        this.cb = CharBuffer.allocate(charBufferSize);
     }
 
 
@@ -63,7 +71,7 @@ public class WsOutbound {
      * @throws IOException  If a flush is required and an error occurs writing
      *                      the WebSocket frame to the client
      */
-    public void writeBinaryData(int b) throws IOException {
+    public synchronized void writeBinaryData(int b) throws IOException {
         if (closed) {
             throw new IOException(sm.getString("outbound.closed"));
         }
@@ -88,12 +96,12 @@ public class WsOutbound {
      * message started. If the buffer for textual data is full, the buffer will
      * be flushed and a new textual continuation fragment started.
      *
-     * @param b The character to send to the client.
+     * @param c The character to send to the client.
      *
      * @throws IOException  If a flush is required and an error occurs writing
      *                      the WebSocket frame to the client
      */
-    public void writeTextData(char c) throws IOException {
+    public synchronized void writeTextData(char c) throws IOException {
         if (closed) {
             throw new IOException(sm.getString("outbound.closed"));
         }
@@ -122,7 +130,9 @@ public class WsOutbound {
      *
      * @throws IOException  If an error occurs writing to the client
      */
-    public void writeBinaryMessage(ByteBuffer msgBb) throws IOException {
+    public synchronized void writeBinaryMessage(ByteBuffer msgBb)
+            throws IOException {
+
         if (closed) {
             throw new IOException(sm.getString("outbound.closed"));
         }
@@ -141,11 +151,13 @@ public class WsOutbound {
      * a WebSocket text message as a single frame with the provided buffer as
      * the payload of the message.
      *
-     * @param msgBb The buffer containing the payload
+     * @param msgCb The buffer containing the payload
      *
      * @throws IOException  If an error occurs writing to the client
      */
-    public void writeTextMessage(CharBuffer msgCb) throws IOException {
+    public synchronized void writeTextMessage(CharBuffer msgCb)
+            throws IOException {
+
         if (closed) {
             throw new IOException(sm.getString("outbound.closed"));
         }
@@ -164,7 +176,7 @@ public class WsOutbound {
      *
      * @throws IOException  If an error occurs writing to the client
      */
-    public void flush() throws IOException {
+    public synchronized void flush() throws IOException {
         if (closed) {
             throw new IOException(sm.getString("outbound.closed"));
         }
@@ -209,7 +221,7 @@ public class WsOutbound {
                 close(status, frame.getPayLoad());
             } else {
                 // Invalid close code
-                close(1002, null);
+                close(Constants.STATUS_PROTOCOL_ERROR, null);
             }
         } else {
             // No status
@@ -220,9 +232,15 @@ public class WsOutbound {
 
     private boolean validateCloseStatus(int status) {
 
-        if (status == 1000 || status == 1001 || status == 1002 ||
-                status == 1003 || status == 1007 || status == 1008 ||
-                status == 1009 || status == 1010 || status == 1011 ||
+        if (status == Constants.STATUS_CLOSE_NORMAL ||
+                status == Constants.STATUS_SHUTDOWN ||
+                status == Constants.STATUS_PROTOCOL_ERROR ||
+                status == Constants.STATUS_UNEXPECTED_DATA_TYPE ||
+                status == Constants.STATUS_BAD_DATA ||
+                status == Constants.STATUS_POLICY_VIOLATION ||
+                status == Constants.STATUS_MESSAGE_TOO_LARGE ||
+                status == Constants.STATUS_REQUIRED_EXTENSION ||
+                status == Constants.STATUS_UNEXPECTED_CONDITION ||
                 (status > 2999 && status < 5000)) {
             // Other 1xxx reserved / not permitted
             // 2xxx reserved
@@ -245,9 +263,9 @@ public class WsOutbound {
      *
      * @throws IOException  If an error occurs writing to the client
      */
-    public void close(int status, ByteBuffer data) throws IOException {
-        // TODO Think about threading requirements for writing. This is not
-        // currently thread safe and writing almost certainly needs to be.
+    public synchronized void close(int status, ByteBuffer data)
+            throws IOException {
+
         if (closed) {
             return;
         }
@@ -282,9 +300,8 @@ public class WsOutbound {
      *
      * @throws IOException  If an error occurs writing to the client
      */
-    public void pong(ByteBuffer data) throws IOException {
-        // TODO Think about threading requirements for writing. This is not
-        // currently thread safe and writing almost certainly needs to be.
+    public synchronized void pong(ByteBuffer data) throws IOException {
+
         if (closed) {
             throw new IOException(sm.getString("outbound.closed"));
         }
@@ -315,10 +332,6 @@ public class WsOutbound {
     private void doWriteBytes(ByteBuffer buffer, boolean finalFragment)
             throws IOException {
 
-        if (closed) {
-            throw new IOException("Closed");
-        }
-
         // Work out the first byte
         int first = 0x00;
         if (finalFragment) {

Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/AbstractProtocol.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/AbstractProtocol.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/AbstractProtocol.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/AbstractProtocol.java Thu Mar 15 23:39:48 2012
@@ -563,7 +563,18 @@ public abstract class AbstractProtocol i
                     if (state != SocketState.CLOSED && processor.isAsync()) {
                         state = processor.asyncPostProcess();
                     }
-                } while (state == SocketState.ASYNC_END);
+
+                    if (state == SocketState.UPGRADING) {
+                        // Get the UpgradeInbound handler
+                        UpgradeInbound inbound = processor.getUpgradeInbound();
+                        // Release the Http11 processor to be re-used
+                        release(socket, processor, false, false);
+                        // Create the light-weight upgrade processor
+                        processor = createUpgradeProcessor(socket, inbound);
+                        inbound.onUpgradeComplete();
+                    }
+                } while (state == SocketState.ASYNC_END ||
+                        state == SocketState.UPGRADING);
 
                 if (state == SocketState.LONG) {
                     // In the middle of processing a request/response. Keep the
@@ -581,16 +592,7 @@ public abstract class AbstractProtocol i
                     release(socket, processor, false, false);
                 } else if (state == SocketState.UPGRADED) {
                     // Need to keep the connection associated with the processor
-                    upgradePoll(socket, processor);
-                } else if (state == SocketState.UPGRADING) {
-                    // Get the UpgradeInbound handler
-                    UpgradeInbound inbound = processor.getUpgradeInbound();
-                    // Release the Http11 processor to be re-used
-                    release(socket, processor, false, false);
-                    // Create the light-weight upgrade processor
-                    processor = createUpgradeProcessor(socket, inbound);
-                    // Need to keep the connection associated with the processor
-                    upgradePoll(socket, processor);
+                    longPoll(socket, processor);
                 } else {
                     // Connection closed. OK to recycle the processor.
                     if (!(processor instanceof UpgradeProcessor)) {
@@ -629,8 +631,6 @@ public abstract class AbstractProtocol i
                 Processor<S> processor);
         protected abstract void longPoll(SocketWrapper<S> socket,
                 Processor<S> processor);
-        protected abstract void upgradePoll(SocketWrapper<S> socket,
-                Processor<S> processor);
         protected abstract void release(SocketWrapper<S> socket,
                 Processor<S> processor, boolean socketClosing,
                 boolean addToPoller);

Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/ajp/AbstractAjpProtocol.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/ajp/AbstractAjpProtocol.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/ajp/AbstractAjpProtocol.java Thu Mar 15 23:39:48 2012
@@ -91,12 +91,6 @@ public abstract class AbstractAjpProtoco
         }
 
         @Override
-        protected void upgradePoll(SocketWrapper<S> socket,
-                Processor<S> processor) {
-            // TODO Should never happen. ISE?
-        }
-
-        @Override
         protected P createUpgradeProcessor(SocketWrapper<S> socket,
                 UpgradeInbound inbound) {
             // TODO should fail - throw IOE

Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11AprProtocol.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11AprProtocol.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11AprProtocol.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11AprProtocol.java Thu Mar 15 23:39:48 2012
@@ -238,22 +238,20 @@ public class Http11AprProtocol extends A
             connections.put(socket.getSocket(), processor);
 
             if (processor.isAsync()) {
+                // Async
                 socket.setAsync(true);
             } else if (processor.isComet() && proto.endpoint.isRunning()) {
+                // Comet
                 ((AprEndpoint) proto.endpoint).getCometPoller().add(
                         socket.getSocket().longValue(), false);
+            } else {
+                // Upgraded
+                ((AprEndpoint) proto.endpoint).getPoller().add(
+                        socket.getSocket().longValue(), false);
             }
         }
 
         @Override
-        protected void upgradePoll(SocketWrapper<Long> socket,
-                Processor<Long> processor) {
-            connections.put(socket.getSocket(), processor);
-            ((AprEndpoint) proto.endpoint).getPoller().add(
-                    socket.getSocket().longValue(), false);
-        }
-
-        @Override
         protected Http11AprProcessor createProcessor() {
             Http11AprProcessor processor = new Http11AprProcessor(
                     proto.getMaxHttpHeaderSize(), (AprEndpoint)proto.endpoint,

Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11NioProtocol.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11NioProtocol.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11NioProtocol.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11NioProtocol.java Thu Mar 15 23:39:48 2012
@@ -17,7 +17,6 @@
 package org.apache.coyote.http11;
 
 import java.io.IOException;
-import java.nio.channels.SelectionKey;
 import java.nio.channels.SocketChannel;
 import java.util.Iterator;
 
@@ -31,7 +30,6 @@ import org.apache.tomcat.util.net.Abstra
 import org.apache.tomcat.util.net.NioChannel;
 import org.apache.tomcat.util.net.NioEndpoint;
 import org.apache.tomcat.util.net.NioEndpoint.Handler;
-import org.apache.tomcat.util.net.NioEndpoint.KeyAttachment;
 import org.apache.tomcat.util.net.SSLImplementation;
 import org.apache.tomcat.util.net.SecureNioChannel;
 import org.apache.tomcat.util.net.SocketWrapper;
@@ -251,6 +249,7 @@ public class Http11NioProtocol extends A
             } else {
                 // Either:
                 //  - this is comet request
+                //  - this is an upgraded connection
                 //  - the request line/headers have not been completely
                 //    read
                 socket.getSocket().getPoller().add(socket.getSocket());
@@ -287,17 +286,5 @@ public class Http11NioProtocol extends A
             return new UpgradeNioProcessor(socket, inbound,
                     ((Http11NioProtocol) getProtocol()).getEndpoint().getSelectorPool());
         }
-
-        @Override
-        protected void upgradePoll(SocketWrapper<NioChannel> socket,
-                Processor<NioChannel> processor) {
-            connections.put(socket.getSocket(), processor);
-
-            SelectionKey key = socket.getSocket().getIOChannel().keyFor(
-                    socket.getSocket().getPoller().getSelector());
-            key.interestOps(SelectionKey.OP_READ);
-            ((KeyAttachment) socket).interestOps(
-                    SelectionKey.OP_READ);
-        }
     }
 }

Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11Protocol.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11Protocol.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11Protocol.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/Http11Protocol.java Thu Mar 15 23:39:48 2012
@@ -191,11 +191,5 @@ public class Http11Protocol extends Abst
                 throws IOException {
             return new UpgradeBioProcessor(socket, inbound);
         }
-
-        @Override
-        protected void upgradePoll(SocketWrapper<Socket> socket,
-                Processor<Socket> processor) {
-            connections.put(socket.getSocket(), processor);
-        }
     }
 }

Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/LocalStrings.properties?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/LocalStrings.properties (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/LocalStrings.properties Thu Mar 15 23:39:48 2012
@@ -13,4 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+apr.error=Unexpected error [{0}] reading data from the APR/native socket.
+
 nio.eof.error=Unexpected EOF read on the socket
+

Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java Thu Mar 15 23:39:48 2012
@@ -19,14 +19,9 @@ package org.apache.coyote.http11.upgrade
 import java.io.IOException;
 
 import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
 import org.apache.tomcat.util.net.SocketWrapper;
 
-/**
- * Implementation note: The need to extend Http11Processor could probably be
- * removed if the Processor interface was expanded to cover all of the methods
- * required by the AbstractProtocol. That would simplify the code and further
- * reduce the size of instances of this class.
- */
 public class UpgradeAprProcessor extends UpgradeProcessor<Long> {
 
     private final long socket;
@@ -67,13 +62,35 @@ public class UpgradeAprProcessor extends
     @Override
     public int read() throws IOException {
         byte[] bytes = new byte[1];
-        Socket.recv(socket, bytes, 0, 1);
-        return bytes[0];
+        int result = Socket.recv(socket, bytes, 0, 1);
+        if (result == -1) {
+            return -1;
+        } else {
+            return bytes[0] & 0xFF;
+        }
     }
 
 
     @Override
-    public int read(byte[] bytes, int off, int len) throws IOException {
-        return Socket.recv(socket, bytes, off, len);
+    public int read(boolean block, byte[] bytes, int off, int len)
+            throws IOException {
+        if (!block) {
+            Socket.optSet(socket, Socket.APR_SO_NONBLOCK, -1);
+        }
+        try {
+            int result = Socket.recv(socket, bytes, off, len);
+            if (result > 0) {
+                return result;
+            } else if (-result == Status.EAGAIN) {
+                return 0;
+            } else {
+                throw new IOException(sm.getString("apr.error",
+                        Integer.valueOf(-result)));
+            }
+        } finally {
+            if (!block) {
+                Socket.optSet(socket, Socket.APR_SO_NONBLOCK, 0);
+            }
+        }
     }
 }

Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java Thu Mar 15 23:39:48 2012
@@ -23,12 +23,6 @@ import java.net.Socket;
 
 import org.apache.tomcat.util.net.SocketWrapper;
 
-/**
- * Implementation note: The need to extend Http11Processor could probably be
- * removed if the Processor interface was expanded to cover all of the methods
- * required by the AbstractProtocol. That would simplify the code and further
- * reduce the size of instances of this class.
- */
 public class UpgradeBioProcessor extends UpgradeProcessor<Socket> {
 
     private final InputStream inputStream;
@@ -74,7 +68,10 @@ public class UpgradeBioProcessor extends
 
 
     @Override
-    public int read(byte[] bytes, int off, int len) throws IOException {
+    public int read(boolean block, byte[] bytes, int off, int len)
+            throws IOException {
+        // The BIO endpoint always uses blocking IO so the block parameter is
+        // ignored and a blocking read is performed.
         return inputStream.read(bytes, off, len);
     }
 }

Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeInbound.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeInbound.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeInbound.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeInbound.java Thu Mar 15 23:39:48 2012
@@ -28,6 +28,8 @@ public interface UpgradeInbound {
 
     void setUpgradeProcessor(UpgradeProcessor<?> processor);
 
+    void onUpgradeComplete();
+
     SocketState onData() throws IOException;
 
     void setUpgradeOutbound(UpgradeOutbound upgradeOutbound);

Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java Thu Mar 15 23:39:48 2012
@@ -25,16 +25,12 @@ import org.apache.tomcat.util.net.NioEnd
 import org.apache.tomcat.util.net.NioSelectorPool;
 import org.apache.tomcat.util.net.SocketWrapper;
 
-/**
- * Implementation note: The need to extend Http11Processor could probably be
- * removed if the Processor interface was expanded to cover all of the methods
- * required by the AbstractProtocol. That would simplify the code and further
- * reduce the size of instances of this class.
- */
 public class UpgradeNioProcessor extends UpgradeProcessor<NioChannel> {
 
     private final NioChannel nioChannel;
     private final NioSelectorPool pool;
+    private final int maxRead;
+    private final int maxWrite;
 
     public UpgradeNioProcessor(SocketWrapper<NioChannel> wrapper,
             UpgradeInbound upgradeInbound, NioSelectorPool pool) {
@@ -42,6 +38,8 @@ public class UpgradeNioProcessor extends
 
         this.nioChannel = wrapper.getSocket();
         this.pool = pool;
+        this.maxRead = nioChannel.getBufHandler().getReadBuffer().capacity();
+        this.maxWrite = nioChannel.getBufHandler().getWriteBuffer().capacity();
     }
 
 
@@ -82,7 +80,11 @@ public class UpgradeNioProcessor extends
 
     @Override
     public void write(byte[]b, int off, int len) throws IOException {
-        writeToSocket(b, off, len);
+        int written = 0;
+        while (len - written > maxWrite) {
+            written += writeToSocket(b, off + written, maxWrite);
+        }
+        writeToSocket(b, off + written, len - written);
     }
 
     /*
@@ -91,13 +93,22 @@ public class UpgradeNioProcessor extends
     @Override
     public int read() throws IOException {
         byte[] bytes = new byte[1];
-        readSocket(true, bytes, 0, 1);
-        return bytes[0];
+        int result = readSocket(true, bytes, 0, 1);
+        if (result == -1) {
+            return -1;
+        } else {
+            return bytes[0] & 0xFF;
+        }
     }
 
     @Override
-    public int read(byte[] bytes, int off, int len) throws IOException {
-        return readSocket(true, bytes, off, len);
+    public int read(boolean block, byte[] bytes, int off, int len)
+            throws IOException {
+        if (len > maxRead) {
+            return readSocket(block, bytes, off, maxRead);
+        } else {
+            return readSocket(block, bytes, off, len);
+        }
     }
 
 

Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java Thu Mar 15 23:39:48 2012
@@ -22,8 +22,6 @@ import java.io.OutputStream;
 
 /**
  * Allows data to be written to the upgraded connection.
- *
- * TODO: Override more methods for efficiency.
  */
 public class UpgradeOutbound extends OutputStream {
 

Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java Thu Mar 15 23:39:48 2012
@@ -46,8 +46,32 @@ public abstract class UpgradeProcessor<S
     public abstract void write(byte[] b, int off, int len) throws IOException;
 
     // Input methods
+    /**
+     * This is always a blocking read of a single byte.
+     *
+     * @return  The next byte or -1 if the end of the input is reached.
+     *
+     * @throws IOException  If a problem occurs trying to read from the input
+     */
     public abstract int read() throws IOException;
-    public abstract int read(byte[] bytes, int off, int len) throws IOException;
+
+    /**
+     * Read up to len bytes from the input in either blocking or non-blocking
+     * mode (where non-blocking is supported). If the input does not support
+     * non-blocking reads, a blcoking read will be performed.
+     *
+     * @param block
+     * @param bytes
+     * @param off
+     * @param len
+     * @return  The number of bytes read or -1 if the end of the input is
+     *          reached. Non-blocking reads may return zero if no data is
+     *          available. Blocking reads never return zero.
+     *
+     * @throws IOException  If a problem occurs trying to read from the input
+     */
+    public abstract int read(boolean block, byte[] bytes, int off, int len)
+            throws IOException;
 
     @Override
     public final UpgradeInbound getUpgradeInbound() {

Modified: tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoMessage.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoMessage.java?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoMessage.java (original)
+++ tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoMessage.java Thu Mar 15 23:39:48 2012
@@ -20,6 +20,8 @@ import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 
+import javax.servlet.ServletException;
+
 import org.apache.catalina.websocket.MessageInbound;
 import org.apache.catalina.websocket.StreamInbound;
 import org.apache.catalina.websocket.WebSocketServlet;
@@ -28,14 +30,41 @@ import org.apache.catalina.websocket.Web
 public class EchoMessage extends WebSocketServlet {
 
     private static final long serialVersionUID = 1L;
+    private volatile int byteBufSize;
+    private volatile int charBufSize;
+
+    @Override
+    public void init() throws ServletException {
+        super.init();
+        byteBufSize = getInitParameterIntValue("byteBufferMaxSize", 2097152);
+        charBufSize = getInitParameterIntValue("charBufferMaxSize", 2097152);
+    }
+
+    public int getInitParameterIntValue(String name, int defaultValue) {
+        String val = this.getInitParameter(name);
+        int result = defaultValue;
+        try {
+            result = Integer.parseInt(val);
+        }catch (Exception x) {
+        }
+        return result;
+    }
+
+
 
     @Override
     protected StreamInbound createWebSocketInbound(String subProtocol) {
-        return new EchoMessageInbound();
+        return new EchoMessageInbound(byteBufSize,charBufSize);
     }
 
     private static final class EchoMessageInbound extends MessageInbound {
 
+        public EchoMessageInbound(int byteBufferMaxSize, int charBufferMaxSize) {
+            super();
+            setByteBufferMaxSize(byteBufferMaxSize);
+            setCharBufferMaxSize(charBufferMaxSize);
+        }
+
         @Override
         protected void onBinaryMessage(ByteBuffer message) throws IOException {
             getWsOutbound().writeBinaryMessage(message);

Modified: tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Direction.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Direction.java?rev=1301270&r1=1297716&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Direction.java (original)
+++ tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Direction.java Thu Mar 15 23:39:48 2012
@@ -1,21 +1,21 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package websocket.snake;
-
-public enum Direction {
-    NONE, NORTH, SOUTH, EAST, WEST
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package websocket.snake;
+
+public enum Direction {
+    NONE, NORTH, SOUTH, EAST, WEST
+}

Propchange: tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Direction.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Location.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Location.java?rev=1301270&r1=1297716&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Location.java (original)
+++ tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Location.java Thu Mar 15 23:39:48 2012
@@ -1,65 +1,65 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package websocket.snake;
-
-public class Location {
-
-    public int x;
-    public int y;
-
-    public Location(int x, int y) {
-        this.x = x;
-        this.y = y;
-    }
-
-    public Location getAdjacentLocation(Direction direction) {
-        switch (direction) {
-            case NORTH:
-                return new Location(x, y - SnakeWebSocketServlet.GRID_SIZE);
-            case SOUTH:
-                return new Location(x, y + SnakeWebSocketServlet.GRID_SIZE);
-            case EAST:
-                return new Location(x + SnakeWebSocketServlet.GRID_SIZE, y);
-            case WEST:
-                return new Location(x - SnakeWebSocketServlet.GRID_SIZE, y);
-            case NONE:
-                // fall through
-            default:
-                return this;
-        }
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        Location location = (Location) o;
-
-        if (x != location.x) return false;
-        if (y != location.y) return false;
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = x;
-        result = 31 * result + y;
-        return result;
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package websocket.snake;
+
+public class Location {
+
+    public int x;
+    public int y;
+
+    public Location(int x, int y) {
+        this.x = x;
+        this.y = y;
+    }
+
+    public Location getAdjacentLocation(Direction direction) {
+        switch (direction) {
+            case NORTH:
+                return new Location(x, y - SnakeWebSocketServlet.GRID_SIZE);
+            case SOUTH:
+                return new Location(x, y + SnakeWebSocketServlet.GRID_SIZE);
+            case EAST:
+                return new Location(x + SnakeWebSocketServlet.GRID_SIZE, y);
+            case WEST:
+                return new Location(x - SnakeWebSocketServlet.GRID_SIZE, y);
+            case NONE:
+                // fall through
+            default:
+                return this;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Location location = (Location) o;
+
+        if (x != location.x) return false;
+        if (y != location.y) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = x;
+        result = 31 * result + y;
+        return result;
+    }
+}

Propchange: tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Location.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java?rev=1301270&r1=1297716&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java (original)
+++ tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java Thu Mar 15 23:39:48 2012
@@ -1,142 +1,134 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package websocket.snake;
-
-import java.io.IOException;
-import java.nio.CharBuffer;
-import java.util.ArrayDeque;
-import java.util.Collection;
-import java.util.Deque;
-import java.util.Iterator;
-
-import org.apache.catalina.websocket.WsOutbound;
-
-public class Snake {
-
-    private static final int DEFAULT_LENGTH = 6;
-
-    private final int id;
-    private final WsOutbound outbound;
-
-    private Direction direction;
-    private Deque<Location> locations = new ArrayDeque<Location>();
-    private String hexColor;
-
-    public Snake(int id, WsOutbound outbound) {
-        this.id = id;
-        this.outbound = outbound;
-        this.hexColor = SnakeWebSocketServlet.getRandomHexColor();
-        resetState();
-    }
-
-    private void resetState() {
-        this.direction = Direction.NONE;
-        this.locations.clear();
-        Location startLocation = SnakeWebSocketServlet.getRandomLocation();
-        for (int i = 0; i < DEFAULT_LENGTH; i++) {
-            locations.add(startLocation);
-        }
-    }
-
-    private void kill() {
-        resetState();
-        try {
-            CharBuffer response = CharBuffer.wrap("{'type': 'dead'}");
-            outbound.writeTextMessage(response);
-        } catch (IOException ioe) {
-            // Ignore
-        }
-    }
-
-    private void reward() {
-        grow();
-        try {
-            CharBuffer response = CharBuffer.wrap("{'type': 'kill'}");
-            outbound.writeTextMessage(response);
-        } catch (IOException ioe) {
-            // Ignore
-        }
-    }
-
-    public synchronized void update(Collection<Snake> snakes) {
-        Location firstLocation = locations.getFirst();
-        Location nextLocation = firstLocation.getAdjacentLocation(direction);
-        if (nextLocation.x >= SnakeWebSocketServlet.PLAYFIELD_WIDTH) {
-            nextLocation.x = 0;
-        }
-        if (nextLocation.y >= SnakeWebSocketServlet.PLAYFIELD_HEIGHT) {
-            nextLocation.y = 0;
-        }
-        if (nextLocation.x < 0) {
-            nextLocation.x = SnakeWebSocketServlet.PLAYFIELD_WIDTH;
-        }
-        if (nextLocation.y < 0) {
-            nextLocation.y = SnakeWebSocketServlet.PLAYFIELD_HEIGHT;
-        }
-        locations.addFirst(nextLocation);
-        locations.removeLast();
-
-        for (Snake snake : snakes) {
-            if (snake.getId() != getId() &&
-                    colliding(snake.getHeadLocation())) {
-                snake.kill();
-                reward();
-            }
-        }
-    }
-
-    private void grow() {
-        Location lastLocation = locations.getLast();
-        Location newLocation = new Location(lastLocation.x, lastLocation.y);
-        locations.add(newLocation);
-    }
-
-    private boolean colliding(Location location) {
-        return direction != Direction.NONE && locations.contains(location);
-    }
-
-    public void setDirection(Direction direction) {
-        this.direction = direction;
-    }
-
-    public synchronized String getLocationsJson() {
-        StringBuilder sb = new StringBuilder();
-        for (Iterator<Location> iterator = locations.iterator();
-                iterator.hasNext();) {
-            Location location = iterator.next();
-            sb.append(String.format("{x: %d, y: %d}",
-                    Integer.valueOf(location.x), Integer.valueOf(location.y)));
-            if (iterator.hasNext()) {
-                sb.append(',');
-            }
-        }
-        return String.format("{'id':%d,'body':[%s]}",
-                Integer.valueOf(id), sb.toString());
-    }
-
-    public int getId() {
-        return id;
-    }
-
-    public String getHexColor() {
-        return hexColor;
-    }
-
-    public synchronized Location getHeadLocation() {
-        return locations.getFirst();
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package websocket.snake;
+
+import java.io.IOException;
+import java.nio.CharBuffer;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+
+import org.apache.catalina.websocket.WsOutbound;
+
+public class Snake {
+
+    private static final int DEFAULT_LENGTH = 5;
+
+    private final int id;
+    private final WsOutbound outbound;
+
+    private Direction direction;
+    private int length = DEFAULT_LENGTH;
+    private Location head;
+    private Deque<Location> tail = new ArrayDeque<Location>();
+    private String hexColor;
+
+    public Snake(int id, WsOutbound outbound) {
+        this.id = id;
+        this.outbound = outbound;
+        this.hexColor = SnakeWebSocketServlet.getRandomHexColor();
+        resetState();
+    }
+
+    private void resetState() {
+        this.direction = Direction.NONE;
+        this.head = SnakeWebSocketServlet.getRandomLocation();
+        this.tail.clear();
+        this.length = DEFAULT_LENGTH;
+    }
+
+    private synchronized void kill() {
+        resetState();
+        try {
+            CharBuffer response = CharBuffer.wrap("{'type': 'dead'}");
+            outbound.writeTextMessage(response);
+        } catch (IOException ioe) {
+            // Ignore
+        }
+    }
+
+    private synchronized void reward() {
+        length++;
+        try {
+            CharBuffer response = CharBuffer.wrap("{'type': 'kill'}");
+            outbound.writeTextMessage(response);
+        } catch (IOException ioe) {
+            // Ignore
+        }
+    }
+
+    public synchronized void update(Collection<Snake> snakes) {
+        Location nextLocation = head.getAdjacentLocation(direction);
+        if (nextLocation.x >= SnakeWebSocketServlet.PLAYFIELD_WIDTH) {
+            nextLocation.x = 0;
+        }
+        if (nextLocation.y >= SnakeWebSocketServlet.PLAYFIELD_HEIGHT) {
+            nextLocation.y = 0;
+        }
+        if (nextLocation.x < 0) {
+            nextLocation.x = SnakeWebSocketServlet.PLAYFIELD_WIDTH;
+        }
+        if (nextLocation.y < 0) {
+            nextLocation.y = SnakeWebSocketServlet.PLAYFIELD_HEIGHT;
+        }
+        if (direction != Direction.NONE) {
+            tail.addFirst(head);
+            if (tail.size() > length) {
+                tail.removeLast();
+            }
+            head = nextLocation;
+        }
+
+        for (Snake snake : snakes) {
+            if (snake.getTail().contains(head)) {
+                kill();
+                if (id != snake.id) {
+                    snake.reward();
+                }
+            }
+        }
+    }
+
+    public synchronized Collection<Location> getTail() {
+        return tail;
+    }
+
+    public synchronized void setDirection(Direction direction) {
+        this.direction = direction;
+    }
+
+    public synchronized String getLocationsJson() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(String.format("{x: %d, y: %d}",
+                Integer.valueOf(head.x), Integer.valueOf(head.y)));
+        for (Location location : tail) {
+            sb.append(',');
+            sb.append(String.format("{x: %d, y: %d}",
+                    Integer.valueOf(location.x), Integer.valueOf(location.y)));
+        }
+        return String.format("{'id':%d,'body':[%s]}",
+                Integer.valueOf(id), sb.toString());
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getHexColor() {
+        return hexColor;
+    }
+}

Propchange: tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java?rev=1301270&r1=1297716&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java (original)
+++ tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java Thu Mar 15 23:39:48 2012
@@ -101,6 +101,7 @@ public class SnakeWebSocketServlet exten
                 CharBuffer response = CharBuffer.wrap(message);
                 connection.getWsOutbound().writeTextMessage(response);
             } catch (IOException ignore) {
+                // Ignore
             }
         }
     }
@@ -196,13 +197,13 @@ public class SnakeWebSocketServlet exten
         @Override
         protected void onTextMessage(CharBuffer charBuffer) throws IOException {
             String message = charBuffer.toString();
-            if ("left".equals(message)) {
+            if ("west".equals(message)) {
                 snake.setDirection(Direction.WEST);
-            } else if ("up".equals(message)) {
+            } else if ("north".equals(message)) {
                 snake.setDirection(Direction.NORTH);
-            } else if ("right".equals(message)) {
+            } else if ("east".equals(message)) {
                 snake.setDirection(Direction.EAST);
-            } else if ("down".equals(message)) {
+            } else if ("south".equals(message)) {
                 snake.setDirection(Direction.SOUTH);
             }
         }

Propchange: tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/web.xml
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/web.xml?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/web.xml (original)
+++ tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/web.xml Thu Mar 15 23:39:48 2012
@@ -359,10 +359,31 @@
     <servlet>
       <servlet-name>wsEchoMessage</servlet-name>
       <servlet-class>websocket.EchoMessage</servlet-class>
+      <!-- Uncomment the following block to increase the default maximum
+           WebSocket buffer size from 2MB to 20MB which is required for the
+           Autobahn test suite to pass fully. -->
+      <!--
+      <init-param>
+        <param-name>byteBufferMaxSize</param-name>
+        <param-value>20971520</param-value>
+      </init-param>
+      <init-param>
+        <param-name>charBufferMaxSize</param-name>
+        <param-value>20971520</param-value>
+      </init-param>
+      -->
     </servlet>
     <servlet-mapping>
       <servlet-name>wsEchoMessage</servlet-name>
       <url-pattern>/websocket/echoMessage</url-pattern>
     </servlet-mapping>
+    <servlet>
+      <servlet-name>wsSnake</servlet-name>
+      <servlet-class>websocket.snake.SnakeWebSocketServlet</servlet-class>
+    </servlet>
+    <servlet-mapping>
+      <servlet-name>wsSnake</servlet-name>
+      <url-pattern>/websocket/snake</url-pattern>
+    </servlet-mapping>
 
 </web-app>

Modified: tomcat/tc7.0.x/trunk/webapps/examples/websocket/echo.html
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/websocket/echo.html?rev=1301270&r1=1301269&r2=1301270&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/examples/websocket/echo.html (original)
+++ tomcat/tc7.0.x/trunk/webapps/examples/websocket/echo.html Thu Mar 15 23:39:48 2012
@@ -17,38 +17,140 @@
 <!DOCTYPE html>
 <html>
 <head>
-<title>Apache Tomcat WebSocket Examples: Echo</title>
-<script type="text/javascript">
-function echo(target) {
-  if ("WebSocket" in window) {
-    // TODO: Can we use relative URLs?
-    var ws = new WebSocket(target);
-    ws.onopen = function() {
-      ws.send("Connection opened");
-      alert("WebSocket connection opened.");
-      ws.send("Here is a message!");
-    }
-    ws.onmessage = function(event) {
-      alert("Received: " + event.data);
-    }
-    ws.onclose = function() {
-      alert("WebSocket connection closed.");
-    }
-    // TODO: Extend with a text box for users to enter data
-  } else {
-    alert("WebSocket is not supported by this browser.");
-  }
-}
-</script>
+    <title>Apache Tomcat WebSocket Examples: Echo</title>
+    <style type="text/css">
+        #connect-container {
+            float: left;
+            width: 400px
+        }
+
+        #connect-container div {
+            padding: 5px;
+        }
+
+        #console-container {
+            float: left;
+            padding-left: 20px;
+            width: 400px;
+        }
+
+        #console {
+            border: 1px solid #CCCCCC;
+            border-right-color: #999999;
+            border-bottom-color: #999999;
+            height: 170px;
+            overflow-y: scroll;
+            padding: 5px;
+            width: 100%;
+        }
+
+        #console p {
+            padding: 0;
+            margin: 0;
+        }
+    </style>
+    <script type="text/javascript">
+        var ws = null;
+
+        function setConnected(connected) {
+            document.getElementById('connect').disabled = connected;
+            document.getElementById('disconnect').disabled = !connected;
+            document.getElementById('echo').disabled = !connected;
+        }
+
+        function connect() {
+            var target = document.getElementById('target').value;
+            if (target == '') {
+                alert('Please select server side connection implementation.');
+                return;
+            }
+            if ('WebSocket' in window) {
+                ws = new WebSocket(target);
+            } else if ('MozWebSocket' in window) {
+                ws = new MozWebSocket(target);
+            } else {
+                alert('WebSocket is not supported by this browser.');
+                return;
+            }
+            ws.onopen = function () {
+                setConnected(true);
+                log('Info: WebSocket connection opened.');
+            };
+            ws.onmessage = function (event) {
+                log('Received: ' + event.data);
+            };
+            ws.onclose = function () {
+                setConnected(false);
+                log('Info: WebSocket connection closed.');
+            };
+        }
+
+        function disconnect() {
+            if (ws != null) {
+                ws.close();
+                ws = null;
+            }
+            setConnected(false);
+        }
+
+        function echo() {
+            if (ws != null) {
+                var message = document.getElementById('message').value;
+                log('Sent: ' + message);
+                ws.send(message);
+            } else {
+                alert('WebSocket connection not established, please connect.');
+            }
+        }
+
+        function updateTarget(target) {
+            document.getElementById('target').value = 'ws://' + window.location.host + target;
+        }
+
+        function log(message) {
+            var console = document.getElementById('console');
+            var p = document.createElement('p');
+            p.style.wordWrap = 'break-word';
+            p.innerHTML = message;
+            console.appendChild(p);
+            while (console.childNodes.length > 25) {
+                console.removeChild(console.firstChild);
+            }
+            console.scrollTop = console.scrollHeight;
+        }
+    </script>
 </head>
 <body>
-<div id="echoStreamTest">
-<a href="javascript:echo('ws://localhost:8080/examples/websocket/echoStream')">
-Start echo example using streams on the server side</a>
-</div>
-<div id="echoStreamMessage">
-<a href="javascript:echo('ws://localhost:8080/examples/websocket/echoMessage')">
-Start echo example using messages on the server side</a>
+<noscript><h1>Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable
+    Javascript and reload this page!</h1></noscript>
+<div>
+    <div id="connect-container">
+        <div>
+            <span>Connect using:</span>
+            <!-- echo example using streams on the server side -->
+            <input id="radio1" type="radio" name="group1" value="/examples/websocket/echoStream"
+                   onclick="updateTarget(this.value);"> <label for="radio1">streams</label>
+            <!-- echo example using messages on the server side -->
+            <input id="radio2" type="radio" name="group1" value="/examples/websocket/echoMessage"
+                   onclick="updateTarget(this.value);"> <label for="radio2">messages</label>
+        </div>
+        <div>
+            <input id="target" type="text" size="40" style="width: 350px"/>
+        </div>
+        <div>
+            <button id="connect" onclick="connect();">Connect</button>
+            <button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button>
+        </div>
+        <div>
+            <textarea id="message" style="width: 350px">Here is a message!</textarea>
+        </div>
+        <div>
+            <button id="echo" onclick="echo();" disabled="disabled">Echo message</button>
+        </div>
+    </div>
+    <div id="console-container">
+        <div id="console"></div>
+    </div>
 </div>
 </body>
 </html>
\ No newline at end of file



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org