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