Author: remm
Date: Tue Dec 16 00:38:02 2014
New Revision: 1645800
URL: http://svn.apache.org/r1645800
Log:
- Port websocket patches from Tomcat 8.
- 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.
- Note: client extensions support wasn't ported yet.
Added:
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java
Modified:
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Constants.java
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Util.java
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsHandshakeResponse.java
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml
tomcat/tc7.0.x/trunk/webapps/docs/config/systemprops.xml
Added:
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java?rev=1645800&view=auto
==============================================================================
---
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java
(added)
+++
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java
Tue Dec 16 00:38:02 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<Key,V>();
+
+
+ @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<V>(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<V>(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<V>(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/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Constants.java
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Constants.java?rev=1645800&r1=1645799&r2=1645800&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Constants.java
(original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Constants.java Tue
Dec 16 00:38:02 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,11 +59,33 @@ 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";
+ // 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(
"org.apache.tomcat.websocket.STRICT_SPEC_COMPLIANCE");
@@ -69,9 +93,13 @@ public class Constants {
public static final List<Extension> INSTALLED_EXTENSIONS;
static {
- List<Extension> installed = new ArrayList<Extension>(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<Extension>(1);
+ installed.add(new WsExtension("permessage-deflate"));
+ INSTALLED_EXTENSIONS = Collections.unmodifiableList(installed);
+ }
}
private Constants() {
Modified:
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties?rev=1645800&r1=1645799&r2=1645800&view=diff
==============================================================================
---
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties
(original)
+++
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties
Tue Dec 16 00:38:02 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.
@@ -74,6 +76,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/tc7.0.x/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java?rev=1645800&r1=1645799&r2=1645800&view=diff
==============================================================================
---
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
(original)
+++
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
Tue Dec 16 00:38:02 2014
@@ -40,7 +40,11 @@ public class TransformationFactory {
if (PerMessageDeflate.NAME.equals(name)) {
return PerMessageDeflate.negotiate(preferences);
}
- 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/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Util.java
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Util.java?rev=1645800&r1=1645799&r2=1645800&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Util.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Util.java Tue Dec 16
00:38:02 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;
@@ -308,8 +309,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);
@@ -382,45 +382,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(
@@ -428,7 +423,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) {
@@ -437,7 +432,7 @@ public class Util {
endpointConfig,
decoderMatch.getTextDecoders(), new Object[1],
0, false, -1, -1),
- MessageHandlerResultType.TEXT);
+ MessageHandlerResultType.TEXT);
results.add(result);
}
}
@@ -450,6 +445,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) {
@@ -543,15 +566,30 @@ public class Util {
}
+ private static Method getOnMessagePartialMethod(MessageHandler listener) {
+ try {
+ return listener.getClass().getMethod("onMessage", Object.class,
Boolean.TYPE);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException(
+ sm.getString("util.invalidMessageHandler"), e);
+ } catch (SecurityException e) {
+ throw new IllegalArgumentException(
+ sm.getString("util.invalidMessageHandler"), e);
+ }
+ }
+
+
public static class DecoderMatch {
private final List<Class<? extends Decoder>> textDecoders =
new ArrayList<Class<? extends Decoder>>();
private final List<Class<? extends Decoder>> binaryDecoders =
new ArrayList<Class<? extends Decoder>>();
+ 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(
@@ -597,6 +635,11 @@ public class Util {
}
+ public Class<?> getTarget() {
+ return target;
+ }
+
+
public boolean hasMatches() {
return (textDecoders.size() > 0) || (binaryDecoders.size() > 0);
}
Modified:
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsHandshakeResponse.java
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsHandshakeResponse.java?rev=1645800&r1=1645799&r2=1645800&view=diff
==============================================================================
---
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsHandshakeResponse.java
(original)
+++
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsHandshakeResponse.java
Tue Dec 16 00:38:02 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<List<String>>();
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<String>(entry.getValue());
+ this.headers.put(entry.getKey(), values);
+ }
+ }
}
Modified:
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java?rev=1645800&r1=1645799&r2=1645800&view=diff
==============================================================================
---
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
(original)
+++
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
Tue Dec 16 00:38:02 2014
@@ -78,9 +78,9 @@ public abstract class WsRemoteEndpointIm
// Max size of WebSocket header is 14 bytes
private final ByteBuffer headerBuffer = ByteBuffer.allocate(14);
- private final ByteBuffer outputBuffer = ByteBuffer.allocate(8192);
+ private final ByteBuffer outputBuffer =
ByteBuffer.allocate(Constants.DEFAULT_BUFFER_SIZE);
private final CharsetEncoder encoder = new Utf8Encoder();
- private final ByteBuffer encoderBuffer = ByteBuffer.allocate(8192);
+ private final ByteBuffer encoderBuffer =
ByteBuffer.allocate(Constants.DEFAULT_BUFFER_SIZE);
private final AtomicBoolean batchingAllowed = new AtomicBoolean(false);
private volatile long sendTimeout = -1;
private WsSession wsSession;
@@ -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);
}
@@ -547,13 +553,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) {
@@ -596,10 +611,7 @@ public abstract class WsRemoteEndpointIm
throw new EncodeException(obj, sm.getString(
"wsRemoteEndpoint.noEncoder", obj.getClass()));
}
- } catch (EncodeException e) {
- SendResult sr = new SendResult(e);
- completion.onResult(sr);
- } catch (IOException e) {
+ } catch (Exception e) {
SendResult sr = new SendResult(e);
completion.onResult(sr);
}
@@ -893,9 +905,10 @@ public abstract class WsRemoteEndpointIm
private class WsOutputStream extends OutputStream {
private final WsRemoteEndpointImplBase endpoint;
- private final ByteBuffer buffer = ByteBuffer.allocate(8192);
+ 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;
@@ -908,6 +921,7 @@ public abstract class WsRemoteEndpointIm
sm.getString("wsRemoteEndpoint.closedOutputStream"));
}
+ used = true;
if (buffer.remaining() == 0) {
flush();
}
@@ -928,6 +942,7 @@ public abstract class WsRemoteEndpointIm
throw new IndexOutOfBoundsException();
}
+ used = true;
if (buffer.remaining() == 0) {
flush();
}
@@ -950,7 +965,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
@@ -966,9 +985,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();
}
}
@@ -977,9 +998,10 @@ public abstract class WsRemoteEndpointIm
private static class WsWriter extends Writer {
private final WsRemoteEndpointImplBase endpoint;
- private final CharBuffer buffer = CharBuffer.allocate(8192);
+ 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;
@@ -999,6 +1021,7 @@ public abstract class WsRemoteEndpointIm
throw new IndexOutOfBoundsException();
}
+ used = true;
if (buffer.remaining() == 0) {
flush();
}
@@ -1021,7 +1044,9 @@ public abstract class WsRemoteEndpointIm
sm.getString("wsRemoteEndpoint.closedWriter"));
}
- doWrite(false);
+ if (!Constants.STREAMS_DROP_EMPTY_MESSAGES || buffer.position() >
0) {
+ doWrite(false);
+ }
}
@Override
@@ -1037,9 +1062,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/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java?rev=1645800&r1=1645799&r2=1645800&view=diff
==============================================================================
---
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
(original)
+++
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
Tue Dec 16 00:38:02 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
@@ -235,8 +235,6 @@ public class WsWebSocketContainer
clientEndpointConfiguration.getConfigurator().
beforeRequest(reqHeaders);
- ByteBuffer request = createRequest(path, reqHeaders);
-
SocketAddress sa;
if (port == -1) {
if ("ws".equalsIgnoreCase(scheme)) {
@@ -255,6 +253,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<String>(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());
@@ -285,6 +293,7 @@ public class WsWebSocketContainer
ByteBuffer response;
String subProtocol;
boolean success = false;
+
try {
fConnect.get(timeout, TimeUnit.MILLISECONDS);
@@ -311,9 +320,8 @@ public class WsWebSocketContainer
afterResponse(handshakeResponse);
// Sub-protocol
- // Header names are always stored in lower case
List<String> values = handshakeResponse.getHeaders().get(
- Constants.WS_PROTOCOL_HEADER_NAME_LOWER);
+ Constants.WS_PROTOCOL_HEADER_NAME);
if (values == null || values.size() == 0) {
subProtocol = null;
} else if (values.size() == 1) {
@@ -361,6 +369,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;
@@ -561,7 +579,7 @@ public class WsWebSocketContainer
ExecutionException, DeploymentException, EOFException,
TimeoutException {
- Map<String,List<String>> headers = new HashMap<String, List<String>>();
+ Map<String,List<String>> headers = new
CaseInsensitiveKeyMap<List<String>>();
boolean readStatus = false;
boolean readHeaders = false;
Modified:
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java?rev=1645800&r1=1645799&r2=1645800&view=diff
==============================================================================
---
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java
(original)
+++
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java
Tue Dec 16 00:38:02 2014
@@ -118,7 +118,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/tc7.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java?rev=1645800&r1=1645799&r2=1645800&view=diff
==============================================================================
---
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
(original)
+++
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
Tue Dec 16 00:38:02 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<MessageHandlerInfo>();
+ private final List<MessageHandlerInfo> onMessage = new
ArrayList<MessageHandlerInfo>();
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<MessageHandlerInfo>();
+ 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,7 +371,8 @@ public class PojoMethodMapping {
private int indexInputStream = -1;
private int indexReader = -1;
private int indexPrimitive = -1;
- private Map<Integer,PojoPathParam> indexPathParams = new
HashMap<Integer, PojoPathParam>();
+ private Class<?> primitiveType = null;
+ private Map<Integer,PojoPathParam> indexPathParams = new
HashMap<Integer,PojoPathParam>();
private int indexPayload = -1;
private DecoderMatch decoderMatch = null;
private long maxMessageSize = -1;
@@ -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/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java?rev=1645800&r1=1645799&r2=1645800&view=diff
==============================================================================
---
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
(original)
+++
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
Tue Dec 16 00:38:02 2014
@@ -106,7 +106,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;
@@ -128,8 +128,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<Extension>();
+ 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
@@ -186,7 +194,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/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java?rev=1645800&r1=1645799&r2=1645800&view=diff
==============================================================================
---
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
(original)
+++
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
Tue Dec 16 00:38:02 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<String,
List<String>>();
+ Map<String,List<String>> newHeaders = new
CaseInsensitiveKeyMap<List<String>>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
Modified: tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml?rev=1645800&r1=1645799&r2=1645800&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml Tue Dec 16 00:38:02 2014
@@ -212,6 +212,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/tc7.0.x/trunk/webapps/docs/config/systemprops.xml
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/docs/config/systemprops.xml?rev=1645800&r1=1645799&r2=1645800&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/docs/config/systemprops.xml (original)
+++ tomcat/tc7.0.x/trunk/webapps/docs/config/systemprops.xml Tue Dec 16
00:38:02 2014
@@ -562,6 +562,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: [email protected]
For additional commands, e-mail: [email protected]