Author: violetagg Date: Tue Sep 16 20:07:55 2014 New Revision: 1625368 URL: http://svn.apache.org/r1625368 Log: Merged revisions 1604822,1604833 from tomcat/trunk: - Add plumbing to enable permessage-deflate implementation - This is still a work-in-progress - Trivial format change
Modified: tomcat/tc7.0.x/trunk/ (props changed) tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsFrameBase.java tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsFrameClient.java tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsSession.java tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsFrameServer.java tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java Propchange: tomcat/tc7.0.x/trunk/ ------------------------------------------------------------------------------ Merged /tomcat/trunk:r1604822 Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties?rev=1625368&r1=1625367&r2=1625368&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties Tue Sep 16 20:07:55 2014 @@ -57,7 +57,7 @@ wsFrame.notMasked=The client frame was n wsFrame.oneByteCloseCode=The client sent a close frame with a single byte payload which is not valid wsFrame.sessionClosed=The client data can not be processed because the session has already been closed wsFrame.textMessageTooBig=The decoded text message was too big for the output buffer and the endpoint does not support partial messages -wsFrame.wrongRsv=The client frame set the reserved bits to [{0}] which was not supported by this endpoint +wsFrame.wrongRsv=The client frame set the reserved bits to [{0}] for a message with opCode [{1}] which was not supported by this endpoint wsRemoteEndpoint.closed=Message will not be sent because the WebSocket session has been closed wsRemoteEndpoint.closedDuringMessage=The remainder of the message will not be sent because the WebSocket session has been closed Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsFrameBase.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsFrameBase.java?rev=1625368&r1=1625367&r2=1625368&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsFrameBase.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsFrameBase.java Tue Sep 16 20:07:55 2014 @@ -46,6 +46,7 @@ public abstract class WsFrameBase { // Connection level attributes protected final WsSession wsSession; protected final byte[] inputBuffer; + private final Transformation transformation; // Attributes for control messages // Control messages can appear in the middle of other messages so need @@ -84,14 +85,25 @@ public abstract class WsFrameBase { private int readPos = 0; protected int writePos = 0; - public WsFrameBase(WsSession wsSession) { - + public WsFrameBase(WsSession wsSession, Transformation transformation) { inputBuffer = new byte[Constants.DEFAULT_BUFFER_SIZE]; messageBufferBinary = ByteBuffer.allocate(wsSession.getMaxBinaryMessageBufferSize()); messageBufferText = CharBuffer.allocate(wsSession.getMaxTextMessageBufferSize()); this.wsSession = wsSession; + Transformation finalTransformation; + if (isMasked()) { + finalTransformation = new UnmaskTransformation(); + } else { + finalTransformation = new NoopTransformation(); + } + if (transformation == null) { + this.transformation = finalTransformation; + } else { + transformation.setNext(finalTransformation); + this.transformation = transformation; + } } @@ -134,14 +146,14 @@ public abstract class WsFrameBase { int b = inputBuffer[readPos++]; fin = (b & 0x80) > 0; rsv = (b & 0x70) >>> 4; - if (rsv != 0) { - // Note extensions may use rsv bits but currently no extensions are - // supported + opCode = (byte) (b & 0x0F); + if (!transformation.validateRsv(rsv, opCode)) { throw new WsIOException(new CloseReason( CloseCodes.PROTOCOL_ERROR, - sm.getString("wsFrame.wrongRsv", Integer.valueOf(rsv)))); + sm.getString("wsFrame.wrongRsv", Integer.valueOf(rsv), + Integer.valueOf(opCode)))); } - opCode = (byte) (b & 0x0F); + if (Util.isControl(opCode)) { if (!fin) { throw new WsIOException(new CloseReason( @@ -288,7 +300,7 @@ public abstract class WsFrameBase { private boolean processDataControl() throws IOException { - if (!appendPayloadToMessage(controlBufferBinary)) { + if (!transformation.getMoreData(opCode, rsv, controlBufferBinary)) { return false; } controlBufferBinary.flip(); @@ -386,7 +398,7 @@ public abstract class WsFrameBase { private boolean processDataText() throws IOException { // Copy the available data to the buffer - while (!appendPayloadToMessage(messageBufferBinary)) { + while (!transformation.getMoreData(opCode, rsv, messageBufferBinary)) { // Frame not complete - we ran out of something // Convert bytes to UTF-8 messageBufferBinary.flip(); @@ -481,7 +493,7 @@ public abstract class WsFrameBase { private boolean processDataBinary() throws IOException { // Copy the available data to the buffer - while (!appendPayloadToMessage(messageBufferBinary)) { + while (!transformation.getMoreData(opCode, rsv, messageBufferBinary)) { // Frame not complete - what did we run out of? if (readPos == writePos) { // Ran out of input data - get some more @@ -630,34 +642,6 @@ public abstract class WsFrameBase { } - private boolean appendPayloadToMessage(ByteBuffer dest) { - if (isMasked()) { - while (payloadWritten < payloadLength && readPos < writePos && - dest.hasRemaining()) { - byte b = (byte) ((inputBuffer[readPos] ^ mask[maskIndex]) & 0xFF); - maskIndex++; - if (maskIndex == 4) { - maskIndex = 0; - } - readPos++; - payloadWritten++; - dest.put(b); - } - return (payloadWritten == payloadLength); - } else { - long toWrite = Math.min( - payloadLength - payloadWritten, writePos - readPos); - toWrite = Math.min(toWrite, dest.remaining()); - - dest.put(inputBuffer, readPos, (int) toWrite); - readPos += toWrite; - payloadWritten += toWrite; - return (payloadWritten == payloadLength); - - } - } - - private boolean swallowInput() { long toSkip = Math.min(payloadLength - payloadWritten, writePos - readPos); readPos += toSkip; Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsFrameClient.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsFrameClient.java?rev=1625368&r1=1625367&r2=1625368&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsFrameClient.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsFrameClient.java Tue Sep 16 20:07:55 2014 @@ -32,7 +32,8 @@ public class WsFrameClient extends WsFra public WsFrameClient(ByteBuffer response, AsyncChannelWrapper channel, WsSession wsSession) { - super(wsSession); + // TODO Add support for extensions to the client side code + super(wsSession, null); this.response = response; this.channel = channel; this.handler = new WsFrameClientCompletionHandler(); Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsSession.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsSession.java?rev=1625368&r1=1625367&r2=1625368&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsSession.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsSession.java Tue Sep 16 20:07:55 2014 @@ -114,8 +114,8 @@ public class WsSession implements Sessio URI requestUri, Map<String,List<String>> requestParameterMap, String queryString, Principal userPrincipal, String httpSessionId, String subProtocol, Map<String,String> pathParameters, - boolean secure, EndpointConfig endpointConfig) - throws DeploymentException { + boolean secure, EndpointConfig endpointConfig, + Transformation transformation) throws DeploymentException { this.localEndpoint = localEndpoint; this.wsRemoteEndpoint = wsRemoteEndpoint; this.wsRemoteEndpoint.setSession(this); Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java?rev=1625368&r1=1625367&r2=1625368&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java Tue Sep 16 20:07:55 2014 @@ -352,7 +352,7 @@ public class WsWebSocketContainer WsSession wsSession = new WsSession(endpoint, wsRemoteEndpointClient, this, null, null, null, null, null, subProtocol, Collections.<String, String> emptyMap(), secure, - clientEndpointConfiguration); + clientEndpointConfiguration, null); endpoint.onOpen(wsSession, clientEndpointConfiguration); registerSession(endpoint, wsSession); Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java?rev=1625368&r1=1625367&r2=1625368&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java Tue Sep 16 20:07:55 2014 @@ -17,7 +17,9 @@ package org.apache.tomcat.websocket.server; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.websocket.Extension; import javax.websocket.HandshakeResponse; @@ -56,10 +58,13 @@ public class DefaultServerEndpointConfig @Override public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested) { - + Set<String> installedNames = new HashSet<String>(); + for (Extension e : installed) { + installedNames.add(e.getName()); + } List<Extension> result = new ArrayList<Extension>(); for (Extension request : requested) { - if (installed.contains(request)) { + if (installedNames.contains(request.getName())) { result.add(request); } } Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java?rev=1625368&r1=1625367&r2=1625368&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java Tue Sep 16 20:07:55 2014 @@ -21,9 +21,7 @@ import java.nio.charset.StandardCharsets import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; -import java.util.Collections; import java.util.Enumeration; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -44,6 +42,9 @@ import javax.websocket.server.ServerEndp import org.apache.catalina.connector.RequestFacade; import org.apache.tomcat.util.codec.binary.Base64; import org.apache.tomcat.websocket.Constants; +import org.apache.tomcat.websocket.Transformation; +import org.apache.tomcat.websocket.TransformationFactory; +import org.apache.tomcat.websocket.Util; import org.apache.tomcat.websocket.WsHandshakeResponse; import org.apache.tomcat.websocket.pojo.PojoEndpointServer; @@ -88,7 +89,6 @@ public class UpgradeUtil { // validation fails String key; String subProtocol = null; - List<Extension> extensions = Collections.emptyList(); if (!headerContainsToken(req, Constants.CONNECTION_HEADER_NAME, Constants.CONNECTION_HEADER_VALUE)) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST); @@ -121,7 +121,42 @@ public class UpgradeUtil { sec.getSubprotocols(), subProtocols); // Extensions - // Currently no extensions are supported by this implementation + // Should normally only be one header but handle the case of multiple + // headers + List<Extension> extensionsRequested = new ArrayList<Extension>(); + Enumeration<String> extHeaders = req.getHeaders("Sec-WebSocket-Extensions"); + while (extHeaders.hasMoreElements()) { + Util.parseExtensionHeader(extensionsRequested, extHeaders.nextElement()); + } + List<Extension> negotiatedExtensions = sec.getConfigurator().getNegotiatedExtensions( + Constants.INSTALLED_EXTENSIONS, extensionsRequested); + + // Create the Transformations that will be applied to this connection + List<Transformation> transformations = createTransformations(negotiatedExtensions); + + // Build the transformation pipeline + Transformation transformation = null; + StringBuilder responseHeaderExtensions = new StringBuilder(); + boolean first = true; + for (Transformation t : transformations) { + if (first) { + first = false; + } else { + responseHeaderExtensions.append(','); + } + append(responseHeaderExtensions, t.getExtensionResponse()); + if (transformation == null) { + transformation = t; + } else { + transformation.setNext(t); + } + } + + // Now we have the full pipeline, validate the use of the RSV bits. + if (transformation != null && !transformation.validateRsvBits(0)) { + // TODO i18n + throw new ServletException("Incompatible RSV bit usage"); + } // If we got this far, all is good. Accept the connection. resp.setHeader(Constants.UPGRADE_HEADER_NAME, @@ -134,16 +169,8 @@ public class UpgradeUtil { // RFC6455 4.2.2 explicitly states "" is not valid here resp.setHeader("Sec-WebSocket-Protocol", subProtocol); } - if (!extensions.isEmpty()) { - StringBuilder sb = new StringBuilder(); - Iterator<Extension> iter = extensions.iterator(); - // There must be at least one - sb.append(iter.next()); - while (iter.hasNext()) { - sb.append(','); - sb.append(iter.next().getName()); - } - resp.setHeader("Sec-WebSocket-Extensions", sb.toString()); + if (!transformations.isEmpty()) { + resp.setHeader("Sec-WebSocket-Extensions", responseHeaderExtensions.toString()); } WsHandshakeRequest wsRequest = new WsHandshakeRequest(req); @@ -185,13 +212,44 @@ public class UpgradeUtil { WsHttpUpgradeHandler wsHandler = ((RequestFacade) inner).upgrade(WsHttpUpgradeHandler.class); wsHandler.preInit(ep, perSessionServerEndpointConfig, sc, wsRequest, - subProtocol, pathParams, req.isSecure()); + subProtocol, transformation, pathParams, req.isSecure()); } else { throw new ServletException("Upgrade failed"); } } + private static List<Transformation> createTransformations( + List<Extension> negotiatedExtensions) { + + TransformationFactory factory = TransformationFactory.getInstance(); + + List<Transformation> result = new ArrayList<Transformation>(negotiatedExtensions.size()); + + for (Extension extension : negotiatedExtensions) { + result.add(factory.create(extension)); + } + return result; + } + + private static void append(StringBuilder sb, Extension extension) { + if (extension == null || extension.getName() == null || extension.getName().length() == 0) { + return; + } + + sb.append(extension.getName()); + + for (Extension.Parameter p : extension.getParameters()) { + sb.append(';'); + sb.append(p.getName()); + if (p.getValue() != null) { + sb.append('='); + sb.append(p.getValue()); + } + } + } + + /* * This only works for tokens. Quoted strings need more sophisticated * parsing. Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsFrameServer.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsFrameServer.java?rev=1625368&r1=1625367&r2=1625368&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsFrameServer.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsFrameServer.java Tue Sep 16 20:07:55 2014 @@ -20,6 +20,7 @@ import java.io.EOFException; import java.io.IOException; import org.apache.coyote.http11.upgrade.AbstractServletInputStream; +import org.apache.tomcat.websocket.Transformation; import org.apache.tomcat.websocket.WsFrameBase; import org.apache.tomcat.websocket.WsSession; @@ -29,8 +30,9 @@ public class WsFrameServer extends WsFra private final Object connectionReadLock = new Object(); - public WsFrameServer(AbstractServletInputStream sis, WsSession wsSession) { - super(wsSession); + public WsFrameServer(AbstractServletInputStream sis, WsSession wsSession, + Transformation transformation) { + super(wsSession, transformation); this.sis = sis; } Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java?rev=1625368&r1=1625367&r2=1625368&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java Tue Sep 16 20:07:55 2014 @@ -36,6 +36,7 @@ import org.apache.coyote.http11.upgrade. import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.res.StringManager; +import org.apache.tomcat.websocket.Transformation; import org.apache.tomcat.websocket.WsIOException; import org.apache.tomcat.websocket.WsSession; @@ -56,6 +57,7 @@ public class WsHttpUpgradeHandler implem private WsServerContainer webSocketContainer; private WsHandshakeRequest handshakeRequest; private String subProtocol; + private Transformation transformation; private Map<String,String> pathParameters; private boolean secure; private WebConnection connection; @@ -70,13 +72,14 @@ public class WsHttpUpgradeHandler implem public void preInit(Endpoint ep, EndpointConfig endpointConfig, WsServerContainer wsc, WsHandshakeRequest handshakeRequest, - String subProtocol, Map<String,String> pathParameters, - boolean secure) { + String subProtocol, Transformation transformation, + Map<String,String> pathParameters, boolean secure) { this.ep = ep; this.endpointConfig = endpointConfig; this.webSocketContainer = wsc; this.handshakeRequest = handshakeRequest; this.subProtocol = subProtocol; + this.transformation = transformation; this.pathParameters = pathParameters; this.secure = secure; } @@ -120,12 +123,9 @@ public class WsHttpUpgradeHandler implem handshakeRequest.getParameterMap(), handshakeRequest.getQueryString(), handshakeRequest.getUserPrincipal(), httpSessionId, - subProtocol, pathParameters, secure, endpointConfig); - WsFrameServer wsFrame = new WsFrameServer( - sis, - wsSession); - sos.setWriteListener( - new WsWriteListener(this, wsRemoteEndpointServer)); + subProtocol, pathParameters, secure, endpointConfig, transformation); + WsFrameServer wsFrame = new WsFrameServer(sis, wsSession, transformation); + sos.setWriteListener(new WsWriteListener(this, wsRemoteEndpointServer)); ep.onOpen(wsSession, endpointConfig); webSocketContainer.registerSession(ep, wsSession); sis.setReadListener(new WsReadListener(this, wsFrame)); --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org