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

Reply via email to