You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2014/10/08 12:38:33 UTC

svn commit: r1630065 - in /tomcat/trunk: java/org/apache/tomcat/websocket/ java/org/apache/tomcat/websocket/server/ test/org/apache/tomcat/websocket/ webapps/docs/

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


Re: svn commit: r1630065 - in /tomcat/trunk: java/org/apache/tomcat/websocket/ java/org/apache/tomcat/websocket/server/ test/org/apache/tomcat/websocket/ webapps/docs/

Posted by Martin Grigorov <mg...@apache.org>.
On Wed, Oct 8, 2014 at 1:02 PM, Mark Thomas <ma...@apache.org> wrote:

> On 08/10/2014 11:41, Martin Grigorov wrote:
>
> It really helps if you are going to comment on a small part of a large
> commit if you trim the irrelevant parts of the original in your reply.
>

I'll try to remember for the next time.
In my mail client it is a matter of a single click to see what is before or
after such inline comment, so I thought it is easy for others too.


>
> >> 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 =
> >>
> >
> > Why the type is Object ?
>
> Because I got the IDE to generate the code and didn't notice it created
> a String. I'll get that fixed.
>
> Mark
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: dev-help@tomcat.apache.org
>
>

Re: svn commit: r1630065 - in /tomcat/trunk: java/org/apache/tomcat/websocket/ java/org/apache/tomcat/websocket/server/ test/org/apache/tomcat/websocket/ webapps/docs/

Posted by Mark Thomas <ma...@apache.org>.
On 08/10/2014 11:41, Martin Grigorov wrote:

It really helps if you are going to comment on a small part of a large
commit if you trim the irrelevant parts of the original in your reply.

>> 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 =
>>
> 
> Why the type is Object ?

Because I got the IDE to generate the code and didn't notice it created
a String. I'll get that fixed.

Mark


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


Re: svn commit: r1630065 - in /tomcat/trunk: java/org/apache/tomcat/websocket/ java/org/apache/tomcat/websocket/server/ test/org/apache/tomcat/websocket/ webapps/docs/

Posted by Martin Grigorov <mg...@apache.org>.
Hi,

On Wed, Oct 8, 2014 at 12:38 PM, <ma...@apache.org> wrote:

> 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 =
>

Why the type is Object ?
It is enough for the single use later in Map#get() but someone else may
need it as a String later ?


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