Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoakim Erdfelt2013-12-13 19:28:15 +0000
committerJoakim Erdfelt2013-12-13 19:30:26 +0000
commit46ef022cf47c19eaea72ccb336ae436e5eaebeff (patch)
treeddabfe715891ce54f219a4a74911ea9517bb8adc /jetty-websocket
parent5c296c99d835b96caf4502145abbd897195fc70d (diff)
downloadorg.eclipse.jetty.project-46ef022cf47c19eaea72ccb336ae436e5eaebeff.tar.gz
org.eclipse.jetty.project-46ef022cf47c19eaea72ccb336ae436e5eaebeff.tar.xz
org.eclipse.jetty.project-46ef022cf47c19eaea72ccb336ae436e5eaebeff.zip
421314 - Websocket / Connect attempt with Chrome 32+ fails with "Some extension already uses the compress bit"
+ Reworked extension negotiation to be more consistent with the changes to the spec that Chrome 32 are introducing. Namely that first extension to claim RSV bit wins, all other conflicting extensions are ignored.
Diffstat (limited to 'jetty-websocket')
-rw-r--r--jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java11
-rw-r--r--jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java146
-rw-r--r--jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java39
-rw-r--r--jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java18
-rw-r--r--jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java5
-rw-r--r--jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties2
-rw-r--r--jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java13
7 files changed, 185 insertions, 49 deletions
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
index 8729131d34..f72359982b 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
@@ -187,7 +187,7 @@ public class UpgradeRequest
{
return Collections.unmodifiableMap(parameters);
}
-
+
public String getProtocolVersion()
{
String version = getHeader("Sec-WebSocket-Version");
@@ -265,6 +265,15 @@ public class UpgradeRequest
this.cookies = cookies;
}
+ public void setExtensions(List<ExtensionConfig> configs)
+ {
+ this.extensions.clear();
+ if (configs != null)
+ {
+ this.extensions.addAll(configs);
+ }
+ }
+
public void setHeader(String name, List<String> values)
{
headers.put(name,values);
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java
index 329dbea81c..d7ade452aa 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java
@@ -18,8 +18,11 @@
package org.eclipse.jetty.websocket.api.extensions;
+import java.util.ArrayList;
+import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -30,39 +33,99 @@ import org.eclipse.jetty.websocket.api.util.QuoteUtil;
*/
public class ExtensionConfig
{
+ /**
+ * Parse a single parameterized name.
+ *
+ * @param parameterizedName
+ * the parameterized name
+ * @return the ExtensionConfig
+ */
public static ExtensionConfig parse(String parameterizedName)
{
- Iterator<String> extListIter = QuoteUtil.splitAt(parameterizedName,";");
- String extToken = extListIter.next();
+ return new ExtensionConfig(parameterizedName);
+ }
- ExtensionConfig ext = new ExtensionConfig(extToken);
+ /**
+ * Parse enumeration of <code>Sec-WebSocket-Extensions</code> header values into a {@link ExtensionConfig} list
+ *
+ * @param valuesEnum
+ * the raw header values enum
+ * @return the list of extension configs
+ */
+ public static List<ExtensionConfig> parseEnum(Enumeration<String> valuesEnum)
+ {
+ List<ExtensionConfig> configs = new ArrayList<>();
- // now for parameters
- while (extListIter.hasNext())
+ if (valuesEnum != null)
{
- String extParam = extListIter.next();
- Iterator<String> extParamIter = QuoteUtil.splitAt(extParam,"=");
- String key = extParamIter.next().trim();
- String value = null;
- if (extParamIter.hasNext())
+ while (valuesEnum.hasMoreElements())
{
- value = extParamIter.next();
+ Iterator<String> extTokenIter = QuoteUtil.splitAt(valuesEnum.nextElement(),",");
+ while (extTokenIter.hasNext())
+ {
+ String extToken = extTokenIter.next();
+ configs.add(ExtensionConfig.parse(extToken));
+ }
}
- ext.setParameter(key,value);
}
- return ext;
+ return configs;
}
- private final String name;
- private Map<String, String> parameters;
+ /**
+ * Parse 1 or more raw <code>Sec-WebSocket-Extensions</code> header values into a {@link ExtensionConfig} list
+ *
+ * @param rawSecWebSocketExtensions
+ * the raw header values
+ * @return the list of extension configs
+ */
+ public static List<ExtensionConfig> parseList(String... rawSecWebSocketExtensions)
+ {
+ List<ExtensionConfig> configs = new ArrayList<>();
+
+ for (String rawValue : rawSecWebSocketExtensions)
+ {
+ Iterator<String> extTokenIter = QuoteUtil.splitAt(rawValue,",");
+ while (extTokenIter.hasNext())
+ {
+ String extToken = extTokenIter.next();
+ configs.add(ExtensionConfig.parse(extToken));
+ }
+ }
+
+ return configs;
+ }
- public ExtensionConfig(String name)
+ /**
+ * Convert a list of {@link ExtensionConfig} to a header value
+ *
+ * @param configs
+ * the list of extension configs
+ * @return the header value (null if no configs present)
+ */
+ public static String toHeaderValue(List<ExtensionConfig> configs)
{
- this.name = name;
- this.parameters = new HashMap<>();
+ if ((configs == null) || (configs.isEmpty()))
+ {
+ return null;
+ }
+ StringBuilder parameters = new StringBuilder();
+ boolean needsDelim = false;
+ for (ExtensionConfig ext : configs)
+ {
+ if (needsDelim)
+ {
+ parameters.append(", ");
+ }
+ parameters.append(ext.getParameterizedName());
+ needsDelim = true;
+ }
+ return parameters.toString();
}
+ private final String name;
+ private final Map<String, String> parameters;
+
/**
* Copy constructor
*/
@@ -73,12 +136,33 @@ public class ExtensionConfig
this.parameters.putAll(copy.parameters);
}
+ public ExtensionConfig(String parameterizedName)
+ {
+ Iterator<String> extListIter = QuoteUtil.splitAt(parameterizedName,";");
+ this.name = extListIter.next();
+ this.parameters = new HashMap<>();
+
+ // now for parameters
+ while (extListIter.hasNext())
+ {
+ String extParam = extListIter.next();
+ Iterator<String> extParamIter = QuoteUtil.splitAt(extParam,"=");
+ String key = extParamIter.next().trim();
+ String value = null;
+ if (extParamIter.hasNext())
+ {
+ value = extParamIter.next();
+ }
+ parameters.put(key,value);
+ }
+ }
+
public String getName()
{
return name;
}
- public int getParameter(String key, int defValue)
+ public final int getParameter(String key, int defValue)
{
String val = parameters.get(key);
if (val == null)
@@ -88,7 +172,7 @@ public class ExtensionConfig
return Integer.valueOf(val);
}
- public String getParameter(String key, String defValue)
+ public final String getParameter(String key, String defValue)
{
String val = parameters.get(key);
if (val == null)
@@ -98,7 +182,7 @@ public class ExtensionConfig
return val;
}
- public String getParameterizedName()
+ public final String getParameterizedName()
{
StringBuilder str = new StringBuilder();
str.append(name);
@@ -116,7 +200,7 @@ public class ExtensionConfig
return str.toString();
}
- public Set<String> getParameterKeys()
+ public final Set<String> getParameterKeys()
{
return parameters.keySet();
}
@@ -126,7 +210,7 @@ public class ExtensionConfig
*
* @return the parameter map
*/
- public Map<String, String> getParameters()
+ public final Map<String, String> getParameters()
{
return parameters;
}
@@ -137,25 +221,25 @@ public class ExtensionConfig
* @param other
* the other configuration.
*/
- public void init(ExtensionConfig other)
+ public final void init(ExtensionConfig other)
{
this.parameters.clear();
this.parameters.putAll(other.parameters);
}
- public void setParameter(String key, int value)
+ public final void setParameter(String key)
{
- parameters.put(key,Integer.toString(value));
+ parameters.put(key,null);
}
- public void setParameter(String key, String value)
+ public final void setParameter(String key, int value)
{
- parameters.put(key,value);
+ parameters.put(key,Integer.toString(value));
}
-
- public void setParameter(String key)
+
+ public final void setParameter(String key, String value)
{
- parameters.put(key,null);
+ parameters.put(key,value);
}
@Override
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java
index ef39b3394d..b0344550da 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java
@@ -46,6 +46,7 @@ import org.eclipse.jetty.websocket.common.Parser;
public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames, OutgoingFrames
{
private static final Logger LOG = Log.getLogger(ExtensionStack.class);
+
private final ExtensionFactory factory;
private List<Extension> extensions;
private IncomingFrames nextIncoming;
@@ -215,6 +216,9 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
{
LOG.debug("Extension Configs={}",configs);
this.extensions = new ArrayList<>();
+
+ String rsvClaims[] = new String[3];
+
for (ExtensionConfig config : configs)
{
Extension ext = factory.newInstance(config);
@@ -223,8 +227,41 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
// Extension not present on this side
continue;
}
+
+ // Check RSV
+ if (ext.isRsv1User() && (rsvClaims[0] != null))
+ {
+ LOG.debug("Not adding extension {}. Extension {} already claimed RSV1",config,rsvClaims[0]);
+ continue;
+ }
+ if (ext.isRsv2User() && (rsvClaims[1] != null))
+ {
+ LOG.debug("Not adding extension {}. Extension {} already claimed RSV2",config,rsvClaims[1]);
+ continue;
+ }
+ if (ext.isRsv3User() && (rsvClaims[2] != null))
+ {
+ LOG.debug("Not adding extension {}. Extension {} already claimed RSV3",config,rsvClaims[2]);
+ continue;
+ }
+
+ // Add Extension
extensions.add(ext);
- LOG.debug("Adding Extension: {}",ext);
+ LOG.debug("Adding Extension: {}",config);
+
+ // Record RSV Claims
+ if (ext.isRsv1User())
+ {
+ rsvClaims[0] = ext.getName();
+ }
+ if (ext.isRsv2User())
+ {
+ rsvClaims[1] = ext.getName();
+ }
+ if (ext.isRsv3User())
+ {
+ rsvClaims[2] = ext.getName();
+ }
}
addBean(extensions);
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java
index 0f9a6aedd9..28b2b5f257 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.common.extensions;
-import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.*;
import java.util.ArrayList;
import java.util.List;
@@ -168,4 +168,20 @@ public class ExtensionStackTest
// Shouldn't cause a NPE.
LOG.debug("Shouldn't cause a NPE: {}",stack.toString());
}
+
+ @Test
+ public void testNegotiateChrome32()
+ {
+ ExtensionStack stack = createExtensionStack();
+
+ String chromeRequest = "permessage-deflate; client_max_window_bits, x-webkit-deflate-frame";
+ List<ExtensionConfig> requestedConfigs = ExtensionConfig.parseList(chromeRequest);
+ stack.negotiate(requestedConfigs);
+
+ List<ExtensionConfig> negotiated = stack.getNegotiatedExtensions();
+ String response = ExtensionConfig.toHeaderValue(negotiated);
+
+ Assert.assertThat("Negotiated Extensions", response, is("permessage-deflate"));
+ LOG.debug("Shouldn't cause a NPE: {}",stack.toString());
+ }
}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java
index aee3a73aa1..85fde179e8 100644
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java
@@ -57,9 +57,10 @@ public class HandshakeRFC6455 implements WebSocketHandshake
if (response.getExtensions() != null)
{
- for (ExtensionConfig ext : response.getExtensions())
+ String value = ExtensionConfig.toHeaderValue(response.getExtensions());
+ if (value != null)
{
- response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName());
+ response.addHeader("Sec-WebSocket-Extensions",value);
}
}
diff --git a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
index 644823c1ba..994503cc1b 100644
--- a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
+++ b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
@@ -3,7 +3,7 @@ org.eclipse.jetty.LEVEL=WARN
# org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG
# org.eclipse.jetty.websocket.LEVEL=DEBUG
-org.eclipse.jetty.websocket.LEVEL=INFO
+# org.eclipse.jetty.websocket.LEVEL=INFO
# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG
# org.eclipse.jetty.websocket.server.ab.LEVEL=DEBUG
# org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java
index 8a908a7860..fa87d1099a 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java
@@ -28,7 +28,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -37,7 +36,6 @@ import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
-import org.eclipse.jetty.websocket.api.util.QuoteUtil;
import org.eclipse.jetty.websocket.api.util.WSURI;
/**
@@ -106,16 +104,7 @@ public class ServletUpgradeRequest extends UpgradeRequest
// Parse Extension Configurations
Enumeration<String> e = request.getHeaders("Sec-WebSocket-Extensions");
- while (e.hasMoreElements())
- {
- Iterator<String> extTokenIter = QuoteUtil.splitAt(e.nextElement(),",");
- while (extTokenIter.hasNext())
- {
- String extToken = extTokenIter.next();
- ExtensionConfig config = ExtensionConfig.parse(extToken);
- addExtensions(config);
- }
- }
+ setExtensions(ExtensionConfig.parseEnum(e));
}
public X509Certificate[] getCertificates()

Back to the top