Author: markt
Date: Wed Oct 8 10:38:33 2014
New Revision: 1630065
URL: http://svn.apache.org/r1630065
Log:
Extend support for permessage-deflate to the WebSocket client implementation.
Modified:
tomcat/trunk/java/org/apache/tomcat/websocket/Constants.java
tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java
tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
tomcat/trunk/java/org/apache/tomcat/websocket/WsFrameClient.java
tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
tomcat/trunk/webapps/docs/changelog.xml
Modified: tomcat/trunk/java/org/apache/tomcat/websocket/Constants.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/Constants.java?rev=1630065&r1=1630064&r2=1630065&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/Constants.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/Constants.java Wed Oct 8
10:38:33 2014
@@ -61,6 +61,8 @@ public class Constants {
WS_PROTOCOL_HEADER_NAME.toLowerCase(Locale.ENGLISH);
public static final String WS_EXTENSIONS_HEADER_NAME =
"Sec-WebSocket-Extensions";
+ public static final Object WS_EXTENSIONS_HEADER_NAME_LOWER =
+ WS_EXTENSIONS_HEADER_NAME.toLowerCase(Locale.ENGLISH);
public static final boolean STRICT_SPEC_COMPLIANCE =
Boolean.getBoolean(
Modified: tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java?rev=1630065&r1=1630064&r2=1630065&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java
(original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java Wed
Oct 8 10:38:33 2014
@@ -48,6 +48,7 @@ public class PerMessageDeflate implement
private final int serverMaxWindowBits;
private final boolean clientContextTakeover;
private final int clientMaxWindowBits;
+ private final boolean isServer;
private final Inflater inflater = new Inflater(true);
private final ByteBuffer readBuffer =
ByteBuffer.allocate(Constants.DEFAULT_BUFFER_SIZE);
private final Deflater deflater = new
Deflater(Deflater.DEFAULT_COMPRESSION, true);
@@ -58,8 +59,8 @@ public class PerMessageDeflate implement
private volatile ByteBuffer writeBuffer =
ByteBuffer.allocate(Constants.DEFAULT_BUFFER_SIZE);
private volatile boolean firstCompressedFrameWritten = false;
- static PerMessageDeflate negotiate(List<List<Parameter>> preferences) {
- // Accept the first preference that the server is able to support
+ static PerMessageDeflate negotiate(List<List<Parameter>> preferences,
boolean isServer) {
+ // Accept the first preference that the endpoint is able to support
for (List<Parameter> preference : preferences) {
boolean ok = true;
boolean serverContextTakeover = true;
@@ -142,7 +143,7 @@ public class PerMessageDeflate implement
}
if (ok) {
return new PerMessageDeflate(serverContextTakeover,
serverMaxWindowBits,
- clientContextTakeover, clientMaxWindowBits);
+ clientContextTakeover, clientMaxWindowBits, isServer);
}
}
// Failed to negotiate agreeable terms
@@ -151,11 +152,12 @@ public class PerMessageDeflate implement
private PerMessageDeflate(boolean serverContextTakeover, int
serverMaxWindowBits,
- boolean clientContextTakeover, int clientMaxWindowBits) {
+ boolean clientContextTakeover, int clientMaxWindowBits, boolean
isServer) {
this.serverContextTakeover = serverContextTakeover;
this.serverMaxWindowBits = serverMaxWindowBits;
this.clientContextTakeover = clientContextTakeover;
this.clientMaxWindowBits = clientMaxWindowBits;
+ this.isServer = isServer;
}
@@ -211,7 +213,8 @@ public class PerMessageDeflate implement
}
}
} else if (written == 0) {
- if (fin && !serverContextTakeover) {
+ if (fin && (isServer && !serverContextTakeover ||
+ !isServer && !clientContextTakeover)) {
inflater.reset();
}
return TransformationResult.END_OF_FRAME;
@@ -423,11 +426,12 @@ public class PerMessageDeflate implement
private void startNewMessage() {
firstCompressedFrameWritten = false;
- if (!clientContextTakeover) {
+ if (isServer && !clientContextTakeover || !isServer &&
!serverContextTakeover) {
deflater.reset();
}
}
+
private int getRsv(MessagePart uncompressedMessagePart) {
int result = uncompressedMessagePart.getRsv();
if (!firstCompressedFrameWritten) {
Modified:
tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java?rev=1630065&r1=1630064&r2=1630065&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
(original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
Wed Oct 8 10:38:33 2014
@@ -36,9 +36,10 @@ public class TransformationFactory {
return factory;
}
- public Transformation create(String name, List<List<Extension.Parameter>>
preferences) {
+ public Transformation create(String name, List<List<Extension.Parameter>>
preferences,
+ boolean isServer) {
if (PerMessageDeflate.NAME.equals(name)) {
- return PerMessageDeflate.negotiate(preferences);
+ return PerMessageDeflate.negotiate(preferences, isServer);
}
throw new IllegalArgumentException(
sm.getString("transformerFactory.unsupportedExtension", name));
Modified: tomcat/trunk/java/org/apache/tomcat/websocket/WsFrameClient.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsFrameClient.java?rev=1630065&r1=1630064&r2=1630065&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/WsFrameClient.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/WsFrameClient.java Wed Oct 8
10:38:33 2014
@@ -31,9 +31,8 @@ public class WsFrameClient extends WsFra
private ByteBuffer response;
public WsFrameClient(ByteBuffer response, AsyncChannelWrapper channel,
- WsSession wsSession) {
- // TODO Add support for extensions to the client side code
- super(wsSession, null);
+ WsSession wsSession, Transformation transformation) {
+ super(wsSession, transformation);
this.response = response;
this.channel = channel;
this.handler = new WsFrameClientCompletionHandler();
Modified:
tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java?rev=1630065&r1=1630064&r2=1630065&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
(original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java Wed
Oct 8 10:38:33 2014
@@ -274,6 +274,9 @@ public class WsWebSocketContainer
ByteBuffer response;
String subProtocol;
boolean success = false;
+ List<Extension> extensionsAgreed = new ArrayList<>();
+ Transformation transformation = null;
+
try {
fConnect.get(timeout, TimeUnit.MILLISECONDS);
@@ -301,16 +304,45 @@ public class WsWebSocketContainer
// Sub-protocol
// Header names are always stored in lower case
- List<String> values = handshakeResponse.getHeaders().get(
+ List<String> protocolHeaders = handshakeResponse.getHeaders().get(
Constants.WS_PROTOCOL_HEADER_NAME_LOWER);
- if (values == null || values.size() == 0) {
+ if (protocolHeaders == null || protocolHeaders.size() == 0) {
subProtocol = null;
- } else if (values.size() == 1) {
- subProtocol = values.get(0);
+ } else if (protocolHeaders.size() == 1) {
+ subProtocol = protocolHeaders.get(0);
} else {
throw new DeploymentException(
sm.getString("Sec-WebSocket-Protocol"));
}
+
+ // Extensions
+ // 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);
+ if (extHeaders != null) {
+ for (String extHeader : extHeaders) {
+ Util.parseExtensionHeader(extensionsAgreed, extHeader);
+ }
+ }
+
+ // Build the transformations
+ TransformationFactory factory =
TransformationFactory.getInstance();
+ for (Extension extension : extensionsAgreed) {
+ List<List<Extension.Parameter>> wrapper = new ArrayList<>(1);
+ wrapper.add(extension.getParameters());
+ Transformation t = factory.create(extension.getName(),
wrapper, false);
+ if (t == null) {
+ // TODO i18n
+ throw new DeploymentException("Client requested parameters
it could not support");
+ }
+ if (transformation == null) {
+ transformation = t;
+ } else {
+ transformation.setNext(t);
+ }
+ }
+
success = true;
} catch (ExecutionException | InterruptedException | SSLException |
EOFException | TimeoutException e) {
@@ -326,12 +358,12 @@ public class WsWebSocketContainer
WsRemoteEndpointImplClient wsRemoteEndpointClient = new
WsRemoteEndpointImplClient(channel);
WsSession wsSession = new WsSession(endpoint, wsRemoteEndpointClient,
- this, null, null, null, null, null,
Collections.<Extension>emptyList(),
+ this, null, null, null, null, null, extensionsAgreed,
subProtocol, Collections.<String,String>emptyMap(), secure,
clientEndpointConfiguration);
WsFrameClient wsFrameClient = new WsFrameClient(response, channel,
- wsSession);
+ wsSession, transformation);
// WsFrame adds the necessary final transformations. Copy the
// completed transformation chain to the remote end point.
wsRemoteEndpointClient.setTransformation(wsFrameClient.getTransformation());
@@ -463,6 +495,7 @@ public class WsWebSocketContainer
header.append(value);
}
}
+ result.add(header.toString());
}
return result;
}
Modified: tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java?rev=1630065&r1=1630064&r2=1630065&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
(original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java Wed
Oct 8 10:38:33 2014
@@ -247,7 +247,7 @@ public class UpgradeUtil {
for (Map.Entry<String,List<List<Extension.Parameter>>> entry :
extensionPreferences.entrySet()) {
- Transformation transformation = factory.create(entry.getKey(),
entry.getValue());
+ Transformation transformation = factory.create(entry.getKey(),
entry.getValue(), true);
if (transformation != null) {
result.add(transformation);
}
Modified:
tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java?rev=1630065&r1=1630064&r2=1630065&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
(original)
+++ tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
Wed Oct 8 10:38:33 2014
@@ -20,6 +20,8 @@ import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -33,6 +35,7 @@ import javax.websocket.ContainerProvider
import javax.websocket.DeploymentException;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
+import javax.websocket.Extension;
import javax.websocket.MessageHandler;
import javax.websocket.OnMessage;
import javax.websocket.Session;
@@ -927,4 +930,47 @@ public class TestWsWebSocketContainer ex
Assert.assertEquals(Boolean.valueOf(expectOpen),
Boolean.valueOf(s.isOpen()));
}
+
+
+ @Test
+ public void testPerMessageDefalteClient() throws Exception {
+ Tomcat tomcat = getTomcatInstance();
+ // Must have a real docBase - just use temp
+ Context ctx =
+ tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+ ctx.addApplicationListener(TesterEchoServer.Config.class.getName());
+ Tomcat.addServlet(ctx, "default", new DefaultServlet());
+ ctx.addServletMapping("/", "default");
+
+ tomcat.start();
+
+ Extension perMessageDeflate = new WsExtension(PerMessageDeflate.NAME);
+ List<Extension> extensions = new ArrayList<>(1);
+ extensions.add(perMessageDeflate);
+
+ ClientEndpointConfig clientConfig =
+
ClientEndpointConfig.Builder.create().extensions(extensions).build();
+
+ WebSocketContainer wsContainer =
+ ContainerProvider.getWebSocketContainer();
+ Session wsSession = wsContainer.connectToServer(
+ TesterProgrammaticEndpoint.class,
+ clientConfig,
+ new URI("ws://localhost:" + getPort() +
+ TesterEchoServer.Config.PATH_ASYNC));
+ CountDownLatch latch = new CountDownLatch(1);
+ BasicText handler = new BasicText(latch);
+ wsSession.addMessageHandler(handler);
+ wsSession.getBasicRemote().sendText(MESSAGE_STRING_1);
+
+ boolean latchResult = handler.getLatch().await(10, TimeUnit.SECONDS);
+
+ Assert.assertTrue(latchResult);
+
+ Queue<String> messages = handler.getMessages();
+ Assert.assertEquals(1, messages.size());
+ Assert.assertEquals(MESSAGE_STRING_1, messages.peek());
+
+ ((WsWebSocketContainer) wsContainer).destroy();
+ }
}
Modified: tomcat/trunk/webapps/docs/changelog.xml
URL:
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1630065&r1=1630064&r2=1630065&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Wed Oct 8 10:38:33 2014
@@ -141,6 +141,10 @@
single pass; either because the buffer is too small or the server sent
the response in multiple packets. (markt)
</fix>
+ <add>
+ Extend support for the <code>permessage-deflate</code> extension to the
+ client implementation.
+ </add>
</changelog>
</subsection>
<subsection name="Web applications">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]