Author: remm
Date: Thu Dec 11 20:10:47 2014
New Revision: 1644747

URL: http://svn.apache.org/r1644747
Log:
- Rebase on websocket from trunk.
- Correctly implement headers case insensitivity.
- Allow optional use of user extensions.
- Allow using partial binary message handlers.
- Limit ping/pong message size.
- Allow configuration of the time interval for the periodic event.
- More accurate annotations processing.
- Allow optional default for origin header in the client.

Added:
    
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java
Modified:
    tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/Constants.java
    
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties
    
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
    tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/Util.java
    
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsHandshakeResponse.java
    
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
    
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
    
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java
    
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
    
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
    
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
    tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml
    tomcat/tc8.0.x/trunk/webapps/docs/config/systemprops.xml

Added: 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java?rev=1644747&view=auto
==============================================================================
--- 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java
 (added)
+++ 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java
 Thu Dec 11 20:10:47 2014
@@ -0,0 +1,208 @@
+/*
+ * 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 org.apache.tomcat.websocket;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * A Map implementation that uses case-insensitive (using {@link
+ * Locale#ENGLISH}) strings as keys.
+ * <p>
+ * Keys must be instances of {@link String}. Note that this means that
+ * <code>null</code> keys are not permitted.
+ * <p>
+ * This implementation is not thread-safe.
+ *
+ * @param <V> Type of values placed in this Map.
+ */
+public class CaseInsensitiveKeyMap<V> extends AbstractMap<String,V> {
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.PACKAGE_NAME);
+
+    private final Map<Key,V> map = new HashMap<>();
+
+
+    @Override
+    public V get(Object key) {
+        return map.get(Key.getInstance(key));
+    }
+
+
+    @Override
+    public V put(String key, V value) {
+        Key caseInsensitiveKey = Key.getInstance(key);
+        if (caseInsensitiveKey == null) {
+            throw new 
NullPointerException(sm.getString("caseInsensitiveKeyMap.nullKey"));
+        }
+        return map.put(caseInsensitiveKey, value);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * <b>Use this method with caution</b>. If the input Map contains duplicate
+     * keys when the keys are compared in a case insensitive manner then some
+     * values will be lost when inserting via this method.
+     */
+    @Override
+    public void putAll(Map<? extends String, ? extends V> m) {
+        super.putAll(m);
+    }
+
+
+    @Override
+    public boolean containsKey(Object key) {
+        return map.containsKey(Key.getInstance(key));
+    }
+
+
+    @Override
+    public V remove(Object key) {
+        return map.remove(Key.getInstance(key));
+    }
+
+
+    @Override
+    public Set<Entry<String, V>> entrySet() {
+        return new EntrySet<>(map.entrySet());
+    }
+
+
+    private static class EntrySet<V> extends AbstractSet<Entry<String,V>> {
+
+        private final Set<Entry<Key,V>> entrySet;
+
+        public EntrySet(Set<Map.Entry<Key,V>> entrySet) {
+            this.entrySet = entrySet;
+        }
+
+        @Override
+        public Iterator<Entry<String,V>> iterator() {
+            return new EntryIterator<>(entrySet.iterator());
+        }
+
+        @Override
+        public int size() {
+            return entrySet.size();
+        }
+    }
+
+
+    private static class EntryIterator<V> implements Iterator<Entry<String,V>> 
{
+
+        private final Iterator<Entry<Key,V>> iterator;
+
+        public EntryIterator(Iterator<Entry<Key,V>> iterator) {
+            this.iterator = iterator;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return iterator.hasNext();
+        }
+
+        @Override
+        public Entry<String,V> next() {
+            Entry<Key,V> entry = iterator.next();
+            return new EntryImpl<>(entry.getKey().getKey(), entry.getValue());
+        }
+
+        @Override
+        public void remove() {
+            iterator.remove();
+        }
+    }
+
+
+    private static class EntryImpl<V> implements Entry<String,V> {
+
+        private final String key;
+        private final V value;
+
+        public EntryImpl(String key, V value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        @Override
+        public String getKey() {
+            return key;
+        }
+
+        @Override
+        public V getValue() {
+            return value;
+        }
+
+        @Override
+        public V setValue(V value) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private static class Key {
+
+        private final String key;
+        private final String lcKey;
+
+        private Key(String key) {
+            this.key = key;
+            this.lcKey = key.toLowerCase(Locale.ENGLISH);
+        }
+
+        public String getKey() {
+            return key;
+        }
+
+        @Override
+        public int hashCode() {
+            return lcKey.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            Key other = (Key) obj;
+            return lcKey.equals(other.lcKey);
+        }
+
+        public static Key getInstance(Object o) {
+            if (o instanceof String) {
+                return new Key((String) o);
+            }
+            return null;
+        }
+    }
+}

Modified: tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/Constants.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/Constants.java?rev=1644747&r1=1644746&r2=1644747&view=diff
==============================================================================
--- tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/Constants.java 
(original)
+++ tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/Constants.java Thu 
Dec 11 20:10:47 2014
@@ -19,7 +19,6 @@ package org.apache.tomcat.websocket;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
 
 import javax.websocket.Extension;
 
@@ -44,12 +43,15 @@ public class Constants {
     static final byte INTERNAL_OPCODE_FLUSH = 0x18;
 
     // Buffers
-    static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
+    static final int DEFAULT_BUFFER_SIZE = Integer.getInteger(
+            "org.apache.tomcat.websocket.DEFAULT_BUFFER_SIZE", 8 * 1024)
+            .intValue();
 
     // Client connection
     public static final String HOST_HEADER_NAME = "Host";
     public static final String UPGRADE_HEADER_NAME = "Upgrade";
     public static final String UPGRADE_HEADER_VALUE = "websocket";
+    public static final String ORIGIN_HEADER_NAME = "Origin";
     public static final String CONNECTION_HEADER_NAME = "Connection";
     public static final String CONNECTION_HEADER_VALUE = "upgrade";
     public static final String WS_VERSION_HEADER_NAME = 
"Sec-WebSocket-Version";
@@ -57,12 +59,32 @@ public class Constants {
     public static final String WS_KEY_HEADER_NAME = "Sec-WebSocket-Key";
     public static final String WS_PROTOCOL_HEADER_NAME =
             "Sec-WebSocket-Protocol";
-    public static final String WS_PROTOCOL_HEADER_NAME_LOWER =
-            WS_PROTOCOL_HEADER_NAME.toLowerCase(Locale.ENGLISH);
     public static final String WS_EXTENSIONS_HEADER_NAME =
             "Sec-WebSocket-Extensions";
-    public static final String WS_EXTENSIONS_HEADER_NAME_LOWER =
-            WS_EXTENSIONS_HEADER_NAME.toLowerCase(Locale.ENGLISH);
+
+    // Configuration for Origin header in client
+    static final String DEFAULT_ORIGIN_HEADER_VALUE =
+            
System.getProperty("org.apache.tomcat.websocket.DEFAULT_ORIGIN_HEADER_VALUE");
+
+    // Configuration for background processing checks intervals
+    static final int DEFAULT_PROCESS_PERIOD = Integer.getInteger(
+            "org.apache.tomcat.websocket.DEFAULT_PROCESS_PERIOD", 10)
+            .intValue();
+
+    /* Configuration for extensions
+     * Note: These options are primarily present to enable this implementation
+     *       to pass compliance tests. They are expected to be removed once
+     *       the WebSocket API includes a mechanism for adding custom 
extensions
+     *       and disabling built-in extensions.
+     */
+    static final boolean DISABLE_BUILTIN_EXTENSIONS =
+            
Boolean.getBoolean("org.apache.tomcat.websocket.DISABLE_BUILTIN_EXTENSIONS");
+    static final boolean ALLOW_UNSUPPORTED_EXTENSIONS =
+            
Boolean.getBoolean("org.apache.tomcat.websocket.ALLOW_UNSUPPORTED_EXTENSIONS");
+
+    // Configuration for stream behavior
+    static final boolean STREAMS_DROP_EMPTY_MESSAGES =
+            
Boolean.getBoolean("org.apache.tomcat.websocket.STREAMS_DROP_EMPTY_MESSAGES");
 
     public static final boolean STRICT_SPEC_COMPLIANCE =
             Boolean.getBoolean(
@@ -71,9 +93,13 @@ public class Constants {
     public static final List<Extension> INSTALLED_EXTENSIONS;
 
     static {
-        List<Extension> installed = new ArrayList<>(1);
-        installed.add(new WsExtension("permessage-deflate"));
-        INSTALLED_EXTENSIONS = Collections.unmodifiableList(installed);
+        if (DISABLE_BUILTIN_EXTENSIONS) {
+            INSTALLED_EXTENSIONS = Collections.unmodifiableList(new 
ArrayList<Extension>());
+        } else {
+            List<Extension> installed = new ArrayList<>(1);
+            installed.add(new WsExtension("permessage-deflate"));
+            INSTALLED_EXTENSIONS = Collections.unmodifiableList(installed);
+        }
     }
 
     private Constants() {

Modified: 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties?rev=1644747&r1=1644746&r2=1644747&view=diff
==============================================================================
--- 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties 
(original)
+++ 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties 
Thu Dec 11 20:10:47 2014
@@ -28,6 +28,8 @@ asyncChannelWrapperSecure.wrongStateWrit
 
 backgroundProcessManager.processFailed=A background process failed
 
+caseInsensitiveKeyMap.nullKey=Null keys are not permitted
+
 perMessageDeflate.deflateFailed=Failed to decompress a compressed WebSocket 
frame
 perMessageDeflate.duplicateParameter=Duplicate definition of the [{0}] 
extension parameter
 perMessageDeflate.invalidWindowSize=An invalid windows of [{1}] size was 
specified for [{0}]. Valid values are whole numbers from 8 to 15 inclusive.
@@ -76,6 +78,7 @@ wsRemoteEndpoint.noEncoder=No encoder sp
 wsRemoteEndpoint.wrongState=The remote endpoint was in state [{0}] which is an 
invalid state for called method
 wsRemoteEndpoint.nullData=Invalid null data argument
 wsRemoteEndpoint.nullHandler=Invalid null handler argument
+wsRemoteEndpoint.tooMuchData=Ping or pong may not send more than 125 bytes
 
 # Note the following message is used as a close reason in a WebSocket control
 # frame and therefore must be 123 bytes (not characters) or less in length.

Modified: 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java?rev=1644747&r1=1644746&r2=1644747&view=diff
==============================================================================
--- 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
 (original)
+++ 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
 Thu Dec 11 20:10:47 2014
@@ -41,7 +41,11 @@ public class TransformationFactory {
         if (PerMessageDeflate.NAME.equals(name)) {
             return PerMessageDeflate.negotiate(preferences, isServer);
         }
-        throw new IllegalArgumentException(
-                sm.getString("transformerFactory.unsupportedExtension", name));
+        if (Constants.ALLOW_UNSUPPORTED_EXTENSIONS) {
+            return null;
+        } else {
+            throw new IllegalArgumentException(
+                    sm.getString("transformerFactory.unsupportedExtension", 
name));
+        }
     }
 }

Modified: tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/Util.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/Util.java?rev=1644747&r1=1644746&r2=1644747&view=diff
==============================================================================
--- tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/Util.java (original)
+++ tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/Util.java Thu Dec 11 
20:10:47 2014
@@ -49,6 +49,7 @@ import javax.websocket.PongMessage;
 import javax.websocket.Session;
 
 import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.websocket.pojo.PojoMessageHandlerPartialBinary;
 import org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBinary;
 import org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeText;
 
@@ -307,8 +308,7 @@ public class Util {
             return Boolean.valueOf(value);
         } else if (type.equals(byte.class) || type.equals(Byte.class)) {
             return Byte.valueOf(value);
-        } else if (value.length() == 1 &&
-                (type.equals(char.class) || type.equals(Character.class))) {
+        } else if (type.equals(char.class) || type.equals(Character.class)) {
             return Character.valueOf(value.charAt(0));
         } else if (type.equals(double.class) || type.equals(Double.class)) {
             return Double.valueOf(value);
@@ -379,45 +379,40 @@ public class Util {
                     new MessageHandlerResult(listener,
                             MessageHandlerResultType.PONG);
             results.add(result);
-        // Relatively simple cases - handler needs wrapping but no decoder to
-        // convert it to one of the types expected by the frame handling code
+        // Handler needs wrapping and optional decoder to convert it to one of
+        // the types expected by the frame handling code
         } else if (byte[].class.isAssignableFrom(target)) {
+            boolean whole = 
MessageHandler.Whole.class.isAssignableFrom(listener.getClass());
             MessageHandlerResult result = new MessageHandlerResult(
-                    new PojoMessageHandlerWholeBinary(listener,
-                            getOnMessageMethod(listener), session,
-                            endpointConfig, null, new Object[1], 0, true, -1,
-                            false, -1),
+                    whole ? new PojoMessageHandlerWholeBinary(listener,
+                                    getOnMessageMethod(listener), session,
+                                    endpointConfig, matchDecoders(target, 
endpointConfig, true),
+                                    new Object[1], 0, true, -1, false, -1) :
+                            new PojoMessageHandlerPartialBinary(listener,
+                                    getOnMessagePartialMethod(listener), 
session,
+                                    new Object[2], 0, true, 1, -1, -1),
                     MessageHandlerResultType.BINARY);
             results.add(result);
         } else if (InputStream.class.isAssignableFrom(target)) {
             MessageHandlerResult result = new MessageHandlerResult(
                     new PojoMessageHandlerWholeBinary(listener,
                             getOnMessageMethod(listener), session,
-                            endpointConfig, null, new Object[1], 0, true, -1,
-                            true, -1),
+                            endpointConfig, matchDecoders(target, 
endpointConfig, true),
+                            new Object[1], 0, true, -1, true, -1),
                     MessageHandlerResultType.BINARY);
             results.add(result);
         } else if (Reader.class.isAssignableFrom(target)) {
             MessageHandlerResult result = new MessageHandlerResult(
                     new PojoMessageHandlerWholeText(listener,
                             getOnMessageMethod(listener), session,
-                            endpointConfig, null, new Object[1], 0, true, -1,
-                            -1),
+                            endpointConfig, matchDecoders(target, 
endpointConfig, false),
+                            new Object[1], 0, true, -1, -1),
                     MessageHandlerResultType.TEXT);
             results.add(result);
         } else {
-        // More complex case - listener that requires a decoder
-            DecoderMatch decoderMatch;
-            try {
-                List<Class<? extends Decoder>> decoders =
-                        endpointConfig.getDecoders();
-                @SuppressWarnings("unchecked")
-                List<DecoderEntry> decoderEntries = getDecoders(
-                        decoders.toArray(new Class[decoders.size()]));
-                decoderMatch = new DecoderMatch(target, decoderEntries);
-            } catch (DeploymentException e) {
-                throw new IllegalArgumentException(e);
-            }
+            // Handler needs wrapping and requires decoder to convert it to one
+            // of the types expected by the frame handling code
+            DecoderMatch decoderMatch = matchDecoders(target, endpointConfig);
             Method m = getOnMessageMethod(listener);
             if (decoderMatch.getBinaryDecoders().size() > 0) {
                 MessageHandlerResult result = new MessageHandlerResult(
@@ -425,7 +420,7 @@ public class Util {
                                 endpointConfig,
                                 decoderMatch.getBinaryDecoders(), new 
Object[1],
                                 0, false, -1, false, -1),
-                        MessageHandlerResultType.BINARY);
+                                MessageHandlerResultType.BINARY);
                 results.add(result);
             }
             if (decoderMatch.getTextDecoders().size() > 0) {
@@ -434,7 +429,7 @@ public class Util {
                                 endpointConfig,
                                 decoderMatch.getTextDecoders(), new Object[1],
                                 0, false, -1, -1),
-                        MessageHandlerResultType.TEXT);
+                                MessageHandlerResultType.TEXT);
                 results.add(result);
             }
         }
@@ -447,6 +442,34 @@ public class Util {
         return results;
     }
 
+    private static List<Class<? extends Decoder>> matchDecoders(Class<?> 
target,
+            EndpointConfig endpointConfig, boolean binary) {
+        DecoderMatch decoderMatch = matchDecoders(target, endpointConfig);
+        if (binary) {
+            if (decoderMatch.getBinaryDecoders().size() > 0) {
+                return decoderMatch.getBinaryDecoders();
+            }
+        } else if (decoderMatch.getTextDecoders().size() > 0) {
+            return decoderMatch.getTextDecoders();
+        }
+        return null;
+    }
+
+    private static DecoderMatch matchDecoders(Class<?> target,
+            EndpointConfig endpointConfig) {
+        DecoderMatch decoderMatch;
+        try {
+            List<Class<? extends Decoder>> decoders =
+                    endpointConfig.getDecoders();
+            @SuppressWarnings("unchecked")
+            List<DecoderEntry> decoderEntries = getDecoders(
+                    decoders.toArray(new Class[decoders.size()]));
+            decoderMatch = new DecoderMatch(target, decoderEntries);
+        } catch (DeploymentException e) {
+            throw new IllegalArgumentException(e);
+        }
+        return decoderMatch;
+    }
 
     public static void parseExtensionHeader(List<Extension> extensions,
             String header) {
@@ -536,6 +559,15 @@ public class Util {
         }
     }
 
+    private static Method getOnMessagePartialMethod(MessageHandler listener) {
+        try {
+            return listener.getClass().getMethod("onMessage", Object.class, 
Boolean.TYPE);
+        } catch (NoSuchMethodException | SecurityException e) {
+            throw new IllegalArgumentException(
+                    sm.getString("util.invalidMessageHandler"), e);
+        }
+    }
+
 
     public static class DecoderMatch {
 
@@ -543,9 +575,10 @@ public class Util {
                 new ArrayList<>();
         private final List<Class<? extends Decoder>> binaryDecoders =
                 new ArrayList<>();
-
+        private final Class<?> target;
 
         public DecoderMatch(Class<?> target, List<DecoderEntry> 
decoderEntries) {
+            this.target = target;
             for (DecoderEntry decoderEntry : decoderEntries) {
                 if (decoderEntry.getClazz().isAssignableFrom(target)) {
                     if (Binary.class.isAssignableFrom(
@@ -591,6 +624,11 @@ public class Util {
         }
 
 
+        public Class<?> getTarget() {
+            return target;
+        }
+
+
         public boolean hasMatches() {
             return (textDecoders.size() > 0) || (binaryDecoders.size() > 0);
         }

Modified: 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsHandshakeResponse.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsHandshakeResponse.java?rev=1644747&r1=1644746&r2=1644747&view=diff
==============================================================================
--- 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsHandshakeResponse.java 
(original)
+++ 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsHandshakeResponse.java 
Thu Dec 11 20:10:47 2014
@@ -16,9 +16,10 @@
  */
 package org.apache.tomcat.websocket;
 
-import java.util.HashMap;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 import javax.websocket.HandshakeResponse;
 
@@ -27,16 +28,22 @@ import javax.websocket.HandshakeResponse
  */
 public class WsHandshakeResponse implements HandshakeResponse {
 
-    private final Map<String,List<String>> headers;
+    private final Map<String,List<String>> headers = new 
CaseInsensitiveKeyMap<>();
 
 
     public WsHandshakeResponse() {
-        this(new HashMap<String,List<String>>());
     }
 
 
     public WsHandshakeResponse(Map<String,List<String>> headers) {
-        this.headers = headers;
+        for (Entry<String,List<String>> entry : headers.entrySet()) {
+            if (this.headers.containsKey(entry.getKey())) {
+                this.headers.get(entry.getKey()).addAll(entry.getValue());
+            } else {
+                List<String> values = new ArrayList<>(entry.getValue());
+                this.headers.put(entry.getKey(), values);
+            }
+        }
     }
 
 

Modified: 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java?rev=1644747&r1=1644746&r2=1644747&view=diff
==============================================================================
--- 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
 (original)
+++ 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
 Thu Dec 11 20:10:47 2014
@@ -168,6 +168,9 @@ public abstract class WsRemoteEndpointIm
     @Override
     public void sendPing(ByteBuffer applicationData) throws IOException,
             IllegalArgumentException {
+        if (applicationData.remaining() > 125) {
+            throw new 
IllegalArgumentException(sm.getString("wsRemoteEndpoint.tooMuchData"));
+        }
         startMessageBlock(Constants.OPCODE_PING, applicationData, true);
     }
 
@@ -175,6 +178,9 @@ public abstract class WsRemoteEndpointIm
     @Override
     public void sendPong(ByteBuffer applicationData) throws IOException,
             IllegalArgumentException {
+        if (applicationData.remaining() > 125) {
+            throw new 
IllegalArgumentException(sm.getString("wsRemoteEndpoint.tooMuchData"));
+        }
         startMessageBlock(Constants.OPCODE_PONG, applicationData, true);
     }
 
@@ -544,13 +550,22 @@ public abstract class WsRemoteEndpointIm
             throw new 
IllegalArgumentException(sm.getString("wsRemoteEndpoint.nullHandler"));
         }
 
-        if (Util.isPrimitive(obj.getClass())) {
+        /*
+         * Note that the implementation will convert primitives and their 
object
+         * equivalents by default but that users are free to specify their own
+         * encoders and decoders for this if they wish.
+         */
+        Encoder encoder = findEncoder(obj);
+        if (encoder == null && Util.isPrimitive(obj.getClass())) {
             String msg = obj.toString();
             sendStringByCompletion(msg, completion);
             return;
         }
-
-        Encoder encoder = findEncoder(obj);
+        if (encoder == null && byte[].class.isAssignableFrom(obj.getClass())) {
+            ByteBuffer msg = ByteBuffer.wrap((byte[]) obj);
+            sendBytesByCompletion(msg, completion);
+            return;
+        }
 
         try {
             if (encoder instanceof Encoder.Text) {
@@ -573,7 +588,7 @@ public abstract class WsRemoteEndpointIm
                 throw new EncodeException(obj, sm.getString(
                         "wsRemoteEndpoint.noEncoder", obj.getClass()));
             }
-        } catch (EncodeException | IOException e) {
+        } catch (Exception e) {
             SendResult sr = new SendResult(e);
             completion.onResult(sr);
         }
@@ -860,12 +875,13 @@ public abstract class WsRemoteEndpointIm
     }
 
 
-    private class WsOutputStream extends OutputStream {
+    private static class WsOutputStream extends OutputStream {
 
         private final WsRemoteEndpointImplBase endpoint;
         private final ByteBuffer buffer = 
ByteBuffer.allocate(Constants.DEFAULT_BUFFER_SIZE);
         private final Object closeLock = new Object();
         private volatile boolean closed = false;
+        private volatile boolean used = false;
 
         public WsOutputStream(WsRemoteEndpointImplBase endpoint) {
             this.endpoint = endpoint;
@@ -898,6 +914,7 @@ public abstract class WsRemoteEndpointIm
                 throw new IndexOutOfBoundsException();
             }
 
+            used = true;
             if (buffer.remaining() == 0) {
                 flush();
             }
@@ -920,7 +937,11 @@ public abstract class WsRemoteEndpointIm
                         sm.getString("wsRemoteEndpoint.closedOutputStream"));
             }
 
-            doWrite(false);
+            // Optimisation. If there is no data to flush then do not send an
+            // empty message.
+            if (!Constants.STREAMS_DROP_EMPTY_MESSAGES || buffer.position() > 
0) {
+                doWrite(false);
+            }
         }
 
         @Override
@@ -936,9 +957,11 @@ public abstract class WsRemoteEndpointIm
         }
 
         private void doWrite(boolean last) throws IOException {
-            buffer.flip();
-            endpoint.startMessageBlock(Constants.OPCODE_BINARY, buffer, last);
-            stateMachine.complete(last);
+            if (!Constants.STREAMS_DROP_EMPTY_MESSAGES || used) {
+                buffer.flip();
+                endpoint.startMessageBlock(Constants.OPCODE_BINARY, buffer, 
last);
+            }
+            endpoint.stateMachine.complete(last);
             buffer.clear();
         }
     }
@@ -950,6 +973,7 @@ public abstract class WsRemoteEndpointIm
         private final CharBuffer buffer = 
CharBuffer.allocate(Constants.DEFAULT_BUFFER_SIZE);
         private final Object closeLock = new Object();
         private volatile boolean closed = false;
+        private volatile boolean used = false;
 
         public WsWriter(WsRemoteEndpointImplBase endpoint) {
             this.endpoint = endpoint;
@@ -969,6 +993,7 @@ public abstract class WsRemoteEndpointIm
                 throw new IndexOutOfBoundsException();
             }
 
+            used = true;
             if (buffer.remaining() == 0) {
                 flush();
             }
@@ -991,7 +1016,9 @@ public abstract class WsRemoteEndpointIm
                         sm.getString("wsRemoteEndpoint.closedWriter"));
             }
 
-            doWrite(false);
+            if (!Constants.STREAMS_DROP_EMPTY_MESSAGES || buffer.position() > 
0) {
+                doWrite(false);
+            }
         }
 
         @Override
@@ -1007,9 +1034,13 @@ public abstract class WsRemoteEndpointIm
         }
 
         private void doWrite(boolean last) throws IOException {
-            buffer.flip();
-            endpoint.sendPartialString(buffer, last);
-            buffer.clear();
+            if (!Constants.STREAMS_DROP_EMPTY_MESSAGES || used) {
+                buffer.flip();
+                endpoint.sendPartialString(buffer, last);
+                buffer.clear();
+            } else {
+                endpoint.stateMachine.complete(last);
+            }
         }
     }
 

Modified: 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java?rev=1644747&r1=1644746&r2=1644747&view=diff
==============================================================================
--- 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java 
(original)
+++ 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java 
Thu Dec 11 20:10:47 2014
@@ -119,7 +119,7 @@ public class WsWebSocketContainer
     private int maxTextMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
     private volatile long defaultMaxSessionIdleTimeout = 0;
     private int backgroundProcessCount = 0;
-    private int processPeriod = 10;
+    private int processPeriod = Constants.DEFAULT_PROCESS_PERIOD;
 
 
     @Override
@@ -224,8 +224,6 @@ public class WsWebSocketContainer
         clientEndpointConfiguration.getConfigurator().
                 beforeRequest(reqHeaders);
 
-        ByteBuffer request = createRequest(path, reqHeaders);
-
         SocketAddress sa;
         if (port == -1) {
             if ("ws".equalsIgnoreCase(scheme)) {
@@ -244,6 +242,16 @@ public class WsWebSocketContainer
             sa = new InetSocketAddress(host, port);
         }
 
+        // Origin header
+        if (Constants.DEFAULT_ORIGIN_HEADER_VALUE != null &&
+                !reqHeaders.containsKey(Constants.ORIGIN_HEADER_NAME)) {
+            List<String> originValues = new ArrayList<>(1);
+            originValues.add(Constants.DEFAULT_ORIGIN_HEADER_VALUE);
+            reqHeaders.put(Constants.ORIGIN_HEADER_NAME, originValues);
+        }
+
+        ByteBuffer request = createRequest(path, reqHeaders);
+
         AsynchronousSocketChannel socketChannel;
         try {
             socketChannel = 
AsynchronousSocketChannel.open(getAsynchronousChannelGroup());
@@ -303,9 +311,8 @@ public class WsWebSocketContainer
                     afterResponse(handshakeResponse);
 
             // Sub-protocol
-            // Header names are always stored in lower case
             List<String> protocolHeaders = handshakeResponse.getHeaders().get(
-                    Constants.WS_PROTOCOL_HEADER_NAME_LOWER);
+                    Constants.WS_PROTOCOL_HEADER_NAME);
             if (protocolHeaders == null || protocolHeaders.size() == 0) {
                 subProtocol = null;
             } else if (protocolHeaders.size() == 1) {
@@ -319,7 +326,7 @@ public class WsWebSocketContainer
             // Should normally only be one header but handle the case of
             // multiple headers
             List<String> extHeaders = handshakeResponse.getHeaders().get(
-                    Constants.WS_EXTENSIONS_HEADER_NAME_LOWER);
+                    Constants.WS_EXTENSIONS_HEADER_NAME);
             if (extHeaders != null) {
                 for (String extHeader : extHeaders) {
                     Util.parseExtensionHeader(extensionsAgreed, extHeader);
@@ -371,6 +378,16 @@ public class WsWebSocketContainer
         endpoint.onOpen(wsSession, clientEndpointConfiguration);
         registerSession(endpoint, wsSession);
 
+        /* It is possible that the server sent one or more messages as soon as
+         * the WebSocket connection was established. Depending on the exact
+         * timing of when those messages were sent they could be sat in the
+         * input buffer waiting to be read and will not trigger a "data
+         * available to read" event. Therefore, it is necessary to process the
+         * input buffer here. Note that this happens on the current thread 
which
+         * means that this thread will be used for any onMessage notifications.
+         * This is a special case. Subsequent "data available to read" events
+         * will be handled by threads from the AsyncChannelGroup's executor.
+         */
         wsFrameClient.startInputProcessing();
 
         return wsSession;
@@ -572,7 +589,7 @@ public class WsWebSocketContainer
             ExecutionException, DeploymentException, EOFException,
             TimeoutException {
 
-        Map<String,List<String>> headers = new HashMap<>();
+        Map<String,List<String>> headers = new CaseInsensitiveKeyMap<>();
 
         boolean readStatus = false;
         boolean readHeaders = false;
@@ -839,7 +856,6 @@ public class WsWebSocketContainer
     public void backgroundProcess() {
         // This method gets called once a second.
         backgroundProcessCount ++;
-
         if (backgroundProcessCount >= processPeriod) {
             backgroundProcessCount = 0;
 

Modified: 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java?rev=1644747&r1=1644746&r2=1644747&view=diff
==============================================================================
--- 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java
 (original)
+++ 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java
 Thu Dec 11 20:10:47 2014
@@ -116,7 +116,7 @@ public abstract class PojoMessageHandler
         if (t instanceof RuntimeException) {
             throw (RuntimeException) t;
         } else {
-            throw new RuntimeException(t);
+            throw new RuntimeException(t.getMessage(), t);
         }
     }
 }

Modified: 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java?rev=1644747&r1=1644746&r2=1644747&view=diff
==============================================================================
--- 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
 (original)
+++ 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
 Thu Dec 11 20:10:47 2014
@@ -22,6 +22,8 @@ import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -64,7 +66,7 @@ public class PojoMethodMapping {
     private final PojoPathParam[] onOpenParams;
     private final PojoPathParam[] onCloseParams;
     private final PojoPathParam[] onErrorParams;
-    private final Set<MessageHandlerInfo> onMessage = new HashSet<>();
+    private final List<MessageHandlerInfo> onMessage = new ArrayList<>();
     private final String wsPath;
 
 
@@ -78,44 +80,106 @@ public class PojoMethodMapping {
         Method open = null;
         Method close = null;
         Method error = null;
-        for (Method method : clazzPojo.getDeclaredMethods()) {
-            if (method.getAnnotation(OnOpen.class) != null) {
-                checkPublic(method);
-                if (open == null) {
-                    open = method;
-                } else {
-                    // Duplicate annotation
-                    throw new DeploymentException(sm.getString(
-                            "pojoMethodMapping.duplicateAnnotation",
-                            OnOpen.class, clazzPojo));
-                }
-            } else if (method.getAnnotation(OnClose.class) != null) {
-                checkPublic(method);
-                if (close == null) {
-                    close = method;
-                } else {
-                    // Duplicate annotation
-                    throw new DeploymentException(sm.getString(
-                            "pojoMethodMapping.duplicateAnnotation",
-                            OnClose.class, clazzPojo));
-                }
-            } else if (method.getAnnotation(OnError.class) != null) {
-                checkPublic(method);
-                if (error == null) {
-                    error = method;
+        Method[] clazzPojoMethods = null;
+        Class<?> currentClazz = clazzPojo;
+        while (!currentClazz.equals(Object.class)) {
+            Method[] currentClazzMethods = currentClazz.getDeclaredMethods();
+            if (currentClazz == clazzPojo) {
+                clazzPojoMethods = currentClazzMethods;
+            }
+            for (Method method : currentClazzMethods) {
+                if (method.getAnnotation(OnOpen.class) != null) {
+                    checkPublic(method);
+                    if (open == null) {
+                        open = method;
+                    } else {
+                        if (currentClazz == clazzPojo ||
+                                (currentClazz != clazzPojo && 
!isMethodOverride(open, method))) {
+                            // Duplicate annotation
+                            throw new DeploymentException(sm.getString(
+                                    "pojoMethodMapping.duplicateAnnotation",
+                                    OnOpen.class, currentClazz));
+                        }
+                    }
+                } else if (method.getAnnotation(OnClose.class) != null) {
+                    checkPublic(method);
+                    if (close == null) {
+                        close = method;
+                    } else {
+                        if (currentClazz == clazzPojo ||
+                                (currentClazz != clazzPojo && 
!isMethodOverride(close, method))) {
+                            // Duplicate annotation
+                            throw new DeploymentException(sm.getString(
+                                    "pojoMethodMapping.duplicateAnnotation",
+                                    OnClose.class, currentClazz));
+                        }
+                    }
+                } else if (method.getAnnotation(OnError.class) != null) {
+                    checkPublic(method);
+                    if (error == null) {
+                        error = method;
+                    } else {
+                        if (currentClazz == clazzPojo ||
+                                (currentClazz != clazzPojo && 
!isMethodOverride(error, method))) {
+                            // Duplicate annotation
+                            throw new DeploymentException(sm.getString(
+                                    "pojoMethodMapping.duplicateAnnotation",
+                                    OnError.class, currentClazz));
+                        }
+                    }
+                } else if (method.getAnnotation(OnMessage.class) != null) {
+                    checkPublic(method);
+                    MessageHandlerInfo messageHandler = new 
MessageHandlerInfo(method, decoders);
+                    boolean found = false;
+                    for (MessageHandlerInfo otherMessageHandler : onMessage) {
+                        if 
(messageHandler.targetsSameWebSocketMessageType(otherMessageHandler)) {
+                            found = true;
+                            if (currentClazz == clazzPojo ||
+                                    (currentClazz != clazzPojo
+                                    && !isMethodOverride(messageHandler.m, 
otherMessageHandler.m))) {
+                                // Duplicate annotation
+                                throw new DeploymentException(sm.getString(
+                                        
"pojoMethodMapping.duplicateAnnotation",
+                                        OnMessage.class, currentClazz));
+                            }
+                        }
+                    }
+                    if (!found) {
+                        onMessage.add(messageHandler);
+                    }
                 } else {
-                    // Duplicate annotation
-                    throw new DeploymentException(sm.getString(
-                            "pojoMethodMapping.duplicateAnnotation",
-                            OnError.class, clazzPojo));
+                    // Method not annotated
                 }
-            } else if (method.getAnnotation(OnMessage.class) != null) {
-                checkPublic(method);
-                onMessage.add(new MessageHandlerInfo(method, decoders));
-            } else {
-                // Method not annotated
+            }
+            currentClazz = currentClazz.getSuperclass();
+        }
+        // If the methods are not on clazzPojo and they are overridden
+        // by a non annotated method in clazzPojo, they should be ignored
+        if (open != null && open.getDeclaringClass() != clazzPojo) {
+            if (isOverridenWithoutAnnotation(clazzPojoMethods, open, 
OnOpen.class)) {
+                open = null;
+            }
+        }
+        if (close != null && close.getDeclaringClass() != clazzPojo) {
+            if (isOverridenWithoutAnnotation(clazzPojoMethods, close, 
OnClose.class)) {
+                close = null;
+            }
+        }
+        if (error != null && error.getDeclaringClass() != clazzPojo) {
+            if (isOverridenWithoutAnnotation(clazzPojoMethods, error, 
OnError.class)) {
+                error = null;
             }
         }
+        List<MessageHandlerInfo> overriddenOnMessage = new ArrayList<>();
+        for (MessageHandlerInfo messageHandler : onMessage) {
+            if (messageHandler.m.getDeclaringClass() != clazzPojo
+                    && isOverridenWithoutAnnotation(clazzPojoMethods, 
messageHandler.m, OnMessage.class)) {
+                overriddenOnMessage.add(messageHandler);
+            }
+        }
+        for (MessageHandlerInfo messageHandler : overriddenOnMessage) {
+            onMessage.remove(messageHandler);
+        }
         this.onOpen = open;
         this.onClose = close;
         this.onError = error;
@@ -133,6 +197,25 @@ public class PojoMethodMapping {
     }
 
 
+    private boolean isMethodOverride(Method method1, Method method2) {
+        return (method1.getName().equals(method2.getName())
+                && method1.getReturnType().equals(method2.getReturnType())
+                && Arrays.equals(method1.getParameterTypes(), 
method2.getParameterTypes()));
+    }
+
+
+    private boolean isOverridenWithoutAnnotation(Method[] methods,
+            Method superclazzMethod, Class<? extends Annotation> annotation) {
+        for (Method method : methods) {
+            if (isMethodOverride(method, superclazzMethod)
+                    && (method.getAnnotation(annotation) == null)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
     public String getWsPath() {
         return wsPath;
     }
@@ -288,6 +371,7 @@ public class PojoMethodMapping {
         private int indexInputStream = -1;
         private int indexReader = -1;
         private int indexPrimitive = -1;
+        private Class<?> primitiveType = null;
         private Map<Integer,PojoPathParam> indexPathParams = new HashMap<>();
         private int indexPayload = -1;
         private DecoderMatch decoderMatch = null;
@@ -366,6 +450,7 @@ public class PojoMethodMapping {
                 } else if (Util.isPrimitive(types[i])) {
                     if (indexPrimitive == -1) {
                         indexPrimitive = i;
+                        primitiveType = types[i];
                     } else {
                         throw new IllegalArgumentException(sm.getString(
                                 "pojoMethodMapping.duplicateMessageParam",
@@ -470,6 +555,7 @@ public class PojoMethodMapping {
                 // The boolean we found is a payload, not a last flag
                 indexPayload = indexBoolean;
                 indexPrimitive = indexBoolean;
+                primitiveType = Boolean.TYPE;
                 indexBoolean = -1;
             }
             if (indexPayload == -1) {
@@ -503,6 +589,40 @@ public class PojoMethodMapping {
         }
 
 
+        public boolean targetsSameWebSocketMessageType(MessageHandlerInfo 
otherHandler) {
+            if (otherHandler == null) {
+                return false;
+            }
+            if (indexByteArray >= 0 && otherHandler.indexByteArray >= 0) {
+                return true;
+            }
+            if (indexByteBuffer >= 0 && otherHandler.indexByteBuffer >= 0) {
+                return true;
+            }
+            if (indexInputStream >= 0 && otherHandler.indexInputStream >= 0) {
+                return true;
+            }
+            if (indexPong >= 0 && otherHandler.indexPong >= 0) {
+                return true;
+            }
+            if (indexPrimitive >= 0 && otherHandler.indexPrimitive >= 0
+                    && primitiveType == otherHandler.primitiveType) {
+                return true;
+            }
+            if (indexReader >= 0 && otherHandler.indexReader >= 0) {
+                return true;
+            }
+            if (indexString >= 0 && otherHandler.indexString >= 0) {
+                return true;
+            }
+            if (decoderMatch != null && otherHandler.decoderMatch != null
+                    && 
decoderMatch.getTarget().equals(otherHandler.decoderMatch.getTarget())) {
+                return true;
+            }
+            return false;
+        }
+
+
         public Set<MessageHandler> getMessageHandlers(Object pojo,
                 Map<String,String> pathParameters, Session session,
                 EndpointConfig config) {

Modified: 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java?rev=1644747&r1=1644746&r2=1644747&view=diff
==============================================================================
--- 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java 
(original)
+++ 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java 
Thu Dec 11 20:10:47 2014
@@ -112,7 +112,7 @@ public class UpgradeUtil {
 
 
         // Origin check
-        String origin = req.getHeader("Origin");
+        String origin = req.getHeader(Constants.ORIGIN_HEADER_NAME);
         if (!sec.getConfigurator().checkOrigin(origin)) {
             resp.sendError(HttpServletResponse.SC_FORBIDDEN);
             return;
@@ -134,8 +134,16 @@ public class UpgradeUtil {
         // Negotiation phase 1. By default this simply filters out the
         // extensions that the server does not support but applications could
         // use a custom configurator to do more than this.
+        List<Extension> installedExtensions = null;
+        if (sec.getExtensions().size() == 0) {
+            installedExtensions = Constants.INSTALLED_EXTENSIONS;
+        } else {
+            installedExtensions = new ArrayList<>();
+            installedExtensions.addAll(sec.getExtensions());
+            installedExtensions.addAll(Constants.INSTALLED_EXTENSIONS);
+        }
         List<Extension> negotiatedExtensionsPhase1 = 
sec.getConfigurator().getNegotiatedExtensions(
-                Constants.INSTALLED_EXTENSIONS, extensionsRequested);
+                installedExtensions, extensionsRequested);
 
         // Negotiation phase 2. Create the Transformations that will be applied
         // to this connection. Note than an extension may be dropped at this
@@ -191,7 +199,7 @@ public class UpgradeUtil {
             resp.setHeader(Constants.WS_EXTENSIONS_HEADER_NAME, 
responseHeaderExtensions.toString());
         }
 
-        WsHandshakeRequest wsRequest = new WsHandshakeRequest(req);
+        WsHandshakeRequest wsRequest = new WsHandshakeRequest(req, pathParams);
         WsHandshakeResponse wsResponse = new WsHandshakeResponse();
         WsPerSessionServerEndpointConfig perSessionServerEndpointConfig =
                 new WsPerSessionServerEndpointConfig(sec);

Modified: 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java?rev=1644747&r1=1644746&r2=1644747&view=diff
==============================================================================
--- 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
 (original)
+++ 
tomcat/tc8.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
 Thu Dec 11 20:10:47 2014
@@ -30,6 +30,8 @@ import java.util.Map.Entry;
 import javax.servlet.http.HttpServletRequest;
 import javax.websocket.server.HandshakeRequest;
 
+import org.apache.tomcat.websocket.CaseInsensitiveKeyMap;
+
 /**
  * Represents the request that this session was opened under.
  */
@@ -45,7 +47,7 @@ public class WsHandshakeRequest implemen
     private volatile HttpServletRequest request;
 
 
-    public WsHandshakeRequest(HttpServletRequest request) {
+    public WsHandshakeRequest(HttpServletRequest request, Map<String,String> 
pathParams) {
 
         this.request = request;
 
@@ -74,10 +76,15 @@ public class WsHandshakeRequest implemen
                     Collections.unmodifiableList(
                             Arrays.asList(entry.getValue())));
         }
+        for (String pathName : pathParams.keySet()) {
+            newParameters.put(pathName,
+                    Collections.unmodifiableList(
+                            Arrays.asList(pathParams.get(pathName))));
+        }
         parameterMap = Collections.unmodifiableMap(newParameters);
 
         // Headers
-        Map<String,List<String>> newHeaders = new HashMap<>();
+        Map<String,List<String>> newHeaders = new CaseInsensitiveKeyMap<>();
 
         Enumeration<String> headerNames = request.getHeaderNames();
         while (headerNames.hasMoreElements()) {

Modified: tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml?rev=1644747&r1=1644746&r2=1644747&view=diff
==============================================================================
--- tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml Thu Dec 11 20:10:47 2014
@@ -202,6 +202,27 @@
         Correct multiple issues with the flushing of batched messages that 
could
         lead to duplicate and/or corrupt messages. (markt)
       </fix>
+      <fix>
+        Correctly implement headers case insensitivity. (markt, remm)
+      </fix>
+      <fix>
+        Allow optional use of user extensions. (remm)
+      </fix>
+      <fix>
+        Allow using partial binary message handlers. (remm)
+      </fix>
+      <fix>
+        Limit ping/pong message size. (remm)
+      </fix>
+      <fix>
+        Allow configuration of the time interval for the periodic event. (remm)
+      </fix>
+      <fix>
+        More accurate annotations processing. (remm)
+      </fix>
+      <fix>
+        Allow optional default for origin header in the client. (remm)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Web applications">

Modified: tomcat/tc8.0.x/trunk/webapps/docs/config/systemprops.xml
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/webapps/docs/config/systemprops.xml?rev=1644747&r1=1644746&r2=1644747&view=diff
==============================================================================
--- tomcat/tc8.0.x/trunk/webapps/docs/config/systemprops.xml (original)
+++ tomcat/tc8.0.x/trunk/webapps/docs/config/systemprops.xml Thu Dec 11 
20:10:47 2014
@@ -589,6 +589,47 @@
 
 </section>
 
+<section name="Websockets">
+
+  <properties>
+
+    <property name="org.apache.tomcat .websocket.ALLOW_UNSUPPORTED_EXTENSIONS">
+      <p>If <code>true</code>, allow unknown extensions to be declared by
+      the user.</p>
+      <p>The default value is <code>false</code>.</p>
+    </property>
+
+    <property name="org.apache.tomcat. websocket.DEFAULT_ORIGIN_HEADER_VALUE">
+      <p>Default value of the origin header that will be sent by the client
+         during the upgrade handshake.</p>
+      <p>The default is null so that no origin header is sent.</p>
+    </property>
+
+    <property name="org.apache.tomcat. websocket.DEFAULT_PROCESS_PERIOD">
+      <p>The number of periodic ticks between periodic processing which
+         involves in particular session expiration checks.</p>
+      <p>The default value is <code>10</code> which corresponds to 10
+         seconds.</p>
+    </property>
+
+    <property name="org.apache.tomcat. websocket.DISABLE_BUILTIN_EXTENSIONS">
+      <p>If <code>true</code>, disable all built-in extensions provided by the
+         server, such as message compression.</p>
+      <p>The default value is <code>false</code>.</p>
+    </property>
+
+    <property name="org.apache.tomcat. websocket.STREAMS_DROP_EMPTY_MESSAGES">
+      <p>If <code>true</code>, streams provided to the user (writer and output
+      stream) will not send an empty message when flushing and there is no
+      data to flush, or when it is closed without having been used (for
+      example if an error occurs).</p>
+      <p>The default value is <code>false</code>.</p>
+    </property>
+
+  </properties>
+
+</section>
+
 <section name="Other">
 
   <properties>



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

Reply via email to