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: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to