Author: markt Date: Mon Jun 23 14:09:47 2014 New Revision: 1604810 URL: http://svn.apache.org/r1604810 Log: Add the initial implementation for the permessage-deflate implementation This is a work-in-progress - There are still multiple Autobahn failures (or rather there will be once it is plumbed in) - No support (yet) for compression of outgoing messages
Added: tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java (with props) tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java (with props) Modified: tomcat/trunk/java/org/apache/tomcat/websocket/Constants.java tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties 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=1604810&r1=1604809&r2=1604810&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/Constants.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/Constants.java Mon Jun 23 14:09:47 2014 @@ -16,8 +16,13 @@ */ package org.apache.tomcat.websocket; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Locale; +import javax.websocket.Extension; + /** * Internal implementation constants. */ @@ -61,6 +66,14 @@ public class Constants { Boolean.getBoolean( "org.apache.tomcat.websocket.STRICT_SPEC_COMPLIANCE"); + public static final List<Extension> INSTALLED_EXTENSIONS; + + static { + List<Extension> installed = new ArrayList<>(1); + installed.add(new WsExtension("permessage-deflate")); + INSTALLED_EXTENSIONS = Collections.unmodifiableList(installed); + } + private Constants() { // Hide default constructor } Modified: tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties?rev=1604810&r1=1604809&r2=1604810&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties Mon Jun 23 14:09:47 2014 @@ -28,6 +28,10 @@ asyncChannelWrapperSecure.wrongStateWrit backgroundProcessManager.processFailed=A background process failed +perMessageDeflate.deflateFailed=Failed to decompress a compressed WebSocket frame +perMessageDeflate.invalidWindowSize=An invalid windows of [{1}] size was specified for [{0}]. Valid values are whole numbers from 8 to 15 inclusive. +perMessageDeflate.unknownParameter=An unknown extension parameter [{0}] was defined + util.notToken=An illegal extension parameter was specified with name [{0}] and value [{1}] util.invalidMessageHandler=The message handler provided does not have an onMessage(Object) method util.invalidType=Unable to coerce value [{0}] to type [{1}]. That type is not supported. Added: 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=1604810&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java (added) +++ tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java Mon Jun 23 14:09:47 2014 @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.websocket; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import javax.websocket.Extension; +import javax.websocket.Extension.Parameter; + +import org.apache.tomcat.util.res.StringManager; + +public class PerMessageDeflate implements Transformation { + + private static final StringManager sm = StringManager.getManager(Constants.PACKAGE_NAME); + + private static final String SERVER_NO_CONTEXT_TAKEOVER = "server_no_context_takeover"; + private static final String CLIENT_NO_CONTEXT_TAKEOVER = "client_no_context_takeover"; + private static final String SERVER_MAX_WINDOW_BITS = "server_max_window_bits"; + private static final String CLIENT_MAX_WINDOW_BITS = "client_max_window_bits"; + + private static final int RSV_BITMASK = 0b100; + private static final byte[] EOM_BYTES = new byte[] {0, 0, -1, -1}; + + public static final String NAME = "permessage-deflate"; + + private boolean serverContextTakeover = true; + private boolean clientContextTakeover = true; + + private final Inflater inflator; + private final ByteBuffer readBuffer = ByteBuffer.allocate(8192); + + private Transformation next; + + PerMessageDeflate(List<Parameter> params) { + + for (Parameter param : params) { + if (SERVER_NO_CONTEXT_TAKEOVER.equals(param.getName())) { + serverContextTakeover = false; + } else if (CLIENT_NO_CONTEXT_TAKEOVER.equals(param.getName())) { + clientContextTakeover = false; + } else if (SERVER_MAX_WINDOW_BITS.equals(param.getName())) { + int bits = Integer.parseInt(param.getValue()); + if (bits < 8 || bits > 15) { + throw new IllegalArgumentException(sm.getString( + "perMessageDeflate.invalidWindowSize", + SERVER_MAX_WINDOW_BITS, Integer.valueOf(bits))); + } + // Java SE API (as of Java 8) does not expose the API to control + // the Window size so decline this option by not including it in + // the response + } else if (CLIENT_MAX_WINDOW_BITS.equals(param.getName())) { + if (param.getValue() != null) { + int bits = Integer.parseInt(param.getValue()); + if (bits < 8 || bits > 15) { + throw new IllegalArgumentException(sm.getString( + "perMessageDeflate.invalidWindowSize", + CLIENT_MAX_WINDOW_BITS, Integer.valueOf(bits))); + } + } + // Java SE API (as of Java 8) does not expose the API to control + // the Window size so decline this option by not including it in + // the response + } else { + // Unknown parameter + throw new IllegalArgumentException(sm.getString( + "perMessageDeflate.unknownParameter", param.getName())); + } + } + + inflator = new Inflater(true); + } + + @Override + public boolean getMoreData(byte opCode, int rsv, ByteBuffer dest) throws IOException { + + // Control frames are never compressed + if (Util.isControl(opCode) || (rsv & RSV_BITMASK) == 0) { + return next.getMoreData(opCode, rsv, dest); + } + + boolean endOfInputFrame = false; + + if (inflator.needsInput()) { + readBuffer.clear(); + endOfInputFrame = next.getMoreData(opCode, (rsv ^ RSV_BITMASK), readBuffer); + inflator.setInput(readBuffer.array(), readBuffer.arrayOffset(), readBuffer.position()); + } + + int written = 0; + try { + written = inflator.inflate(dest.array(), dest.arrayOffset() + dest.position(), dest.remaining()); + if (endOfInputFrame && !inflator.finished()) { + inflator.setInput(EOM_BYTES); + inflator.inflate(dest.array(), dest.arrayOffset() + dest.position(), dest.remaining()); + } + } catch (DataFormatException e) { + throw new IOException(sm.getString("perMessageDeflate.deflateFailed"), e); + } + dest.position(dest.position() + written); + + + if (endOfInputFrame && !clientContextTakeover) { + inflator.reset(); + } + + return endOfInputFrame; + } + + @Override + public boolean validateRsv(int rsv, byte opCode) { + if (Util.isControl(opCode)) { + if ((rsv & RSV_BITMASK) > 0) { + return false; + } else { + if (next == null) { + return true; + } else { + return next.validateRsv(rsv, opCode); + } + } + } else { + int rsvNext = rsv; + if ((rsv & RSV_BITMASK) > 0) { + rsvNext = rsv ^ RSV_BITMASK; + } + if (next == null) { + return true; + } else { + return next.validateRsv(rsvNext, opCode); + } + } + } + + @Override + public Extension getExtensionResponse() { + Extension result = new WsExtension(NAME); + + List<Extension.Parameter> params = result.getParameters(); + + if (!serverContextTakeover) { + params.add(new WsExtensionParameter(SERVER_NO_CONTEXT_TAKEOVER, null)); + } + if (!clientContextTakeover) { + params.add(new WsExtensionParameter(CLIENT_NO_CONTEXT_TAKEOVER, null)); + } + + return result; + } + + @Override + public void setNext(Transformation t) { + if (next == null) { + this.next = t; + } else { + next.setNext(t); + } + } + + @Override + public boolean validateRsvBits(int i) { + if ((i & RSV_BITMASK) > 0) { + return false; + } + if (next == null) { + return true; + } else { + return next.validateRsvBits(i | RSV_BITMASK); + } + } +} Propchange: tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java ------------------------------------------------------------------------------ svn:eol-style = native Added: 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=1604810&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java (added) +++ tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java Mon Jun 23 14:09:47 2014 @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.websocket; + +import javax.websocket.Extension; + +public class TransformationFactory { + + private static final TransformationFactory factory = new TransformationFactory(); + + private TransformationFactory() { + // Hide default constructor + } + + public static TransformationFactory getInstance() { + return factory; + } + + public Transformation create(Extension ext) { + if (PerMessageDeflate.NAME.equals(ext.getName())) { + return new PerMessageDeflate(ext.getParameters()); + } + // TODO i18n + throw new IllegalArgumentException("Unsupported extension"); + } +} Propchange: tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java ------------------------------------------------------------------------------ svn:eol-style = native --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org