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