Author: markt Date: Tue Nov 28 14:22:17 2017 New Revision: 1816549 URL: http://svn.apache.org/viewvc?rev=1816549&view=rev Log: Refactor: Move compression code to new class to allow re-use with HTTP/2
Added: tomcat/trunk/java/org/apache/coyote/CompressionConfig.java (with props) Modified: tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java Added: tomcat/trunk/java/org/apache/coyote/CompressionConfig.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/CompressionConfig.java?rev=1816549&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/coyote/CompressionConfig.java (added) +++ tomcat/trunk/java/org/apache/coyote/CompressionConfig.java Tue Nov 28 14:22:17 2017 @@ -0,0 +1,274 @@ +/* + * 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.coyote; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; +import java.util.regex.Pattern; + +import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.MimeHeaders; + +public class CompressionConfig { + + private int compressionLevel = 0; + private Pattern noCompressionUserAgents = null; + private String compressibleMimeType = "text/html,text/xml,text/plain,text/css," + + "text/javascript,application/javascript,application/json,application/xml"; + private String[] compressibleMimeTypes = null; + private int compressionMinSize = 2048; + + + /** + * Set compression level. + * + * @param compression One of <code>on</code>, <code>force</code>, + * <code>off</code> or the minimum compression size in + * bytes which implies <code>on</code> + */ + public void setCompression(String compression) { + if (compression.equals("on")) { + this.compressionLevel = 1; + } else if (compression.equals("force")) { + this.compressionLevel = 2; + } else if (compression.equals("off")) { + this.compressionLevel = 0; + } else { + try { + // Try to parse compression as an int, which would give the + // minimum compression size + setCompressionMinSize(Integer.parseInt(compression)); + this.compressionLevel = 1; + } catch (Exception e) { + this.compressionLevel = 0; + } + } + } + + + /** + * Return compression level. + * + * @return The current compression level in string form (off/on/force) + */ + public String getCompression() { + switch (compressionLevel) { + case 0: + return "off"; + case 1: + return "on"; + case 2: + return "force"; + } + return "off"; + } + + + public int getCompressionLevel() { + return compressionLevel; + } + + + /** + * Obtain the String form of the regular expression that defines the user + * agents to not use gzip with. + * + * @return The regular expression as a String + */ + public String getNoCompressionUserAgents() { + if (noCompressionUserAgents == null) { + return null; + } else { + return noCompressionUserAgents.toString(); + } + } + + + public Pattern getNoCompressionUserAgentsPattern() { + return noCompressionUserAgents; + } + + + /** + * Set no compression user agent pattern. Regular expression as supported + * by {@link Pattern}. e.g.: <code>gorilla|desesplorer|tigrus</code>. + * + * @param noCompressionUserAgents The regular expression for user agent + * strings for which compression should not + * be applied + */ + public void setNoCompressionUserAgents(String noCompressionUserAgents) { + if (noCompressionUserAgents == null || noCompressionUserAgents.length() == 0) { + this.noCompressionUserAgents = null; + } else { + this.noCompressionUserAgents = + Pattern.compile(noCompressionUserAgents); + } + } + + + public String getCompressibleMimeType() { + return compressibleMimeType; + } + + + public void setCompressibleMimeType(String valueS) { + compressibleMimeType = valueS; + compressibleMimeTypes = null; + } + + + public String[] getCompressibleMimeTypes() { + String[] result = compressibleMimeTypes; + if (result != null) { + return result; + } + List<String> values = new ArrayList<>(); + StringTokenizer tokens = new StringTokenizer(compressibleMimeType, ","); + while (tokens.hasMoreTokens()) { + String token = tokens.nextToken().trim(); + if (token.length() > 0) { + values.add(token); + } + } + result = values.toArray(new String[values.size()]); + compressibleMimeTypes = result; + return result; + } + + + public int getCompressionMinSize() { + return compressionMinSize; + } + + + /** + * Set Minimum size to trigger compression. + * + * @param compressionMinSize The minimum content length required for + * compression in bytes + */ + public void setCompressionMinSize(int compressionMinSize) { + this.compressionMinSize = compressionMinSize; + } + + + /** + * Determines if compression should be enabled for the given response and if + * it is, sets any necessary headers to mark it as such. + * + * @param request The request that triggered the response + * @param response The response to consider compressing + * + * @return {@code true} if compression was enabled for the given response, + * otherwise {@code false} + */ + public boolean useCompression(Request request, Response response) { + // Check if compression is enabled + if (compressionLevel == 0) { + return false; + } + + MimeHeaders responseHeaders = response.getMimeHeaders(); + + // Check if content is not already gzipped + MessageBytes contentEncodingMB = responseHeaders.getValue("Content-Encoding"); + if ((contentEncodingMB != null) && (contentEncodingMB.indexOf("gzip") != -1)) { + return false; + } + + // If force mode, the length and MIME type checks are skipped + if (compressionLevel != 2) { + // Check if the response is of sufficient length to trigger the compression + long contentLength = response.getContentLengthLong(); + if (contentLength != -1 && contentLength < compressionMinSize) { + return false; + } + + // Check for compatible MIME-TYPE + String[] compressibleMimeTypes = this.compressibleMimeTypes; + if (compressibleMimeTypes != null && + !startsWithStringArray(compressibleMimeTypes, response.getContentType())) { + return false; + } + } + + // If processing reaches this far, the response might be compressed. + // Therefore, set the Vary header to keep proxies happy + MessageBytes vary = responseHeaders.getValue("Vary"); + if (vary == null) { + // Add a new Vary header + responseHeaders.setValue("Vary").setString("Accept-Encoding"); + } else if (vary.equals("*")) { + // No action required + } else { + // Merge into current header + responseHeaders.setValue("Vary").setString(vary.getString() + ",Accept-Encoding"); + } + + + // Check if browser support gzip encoding + MessageBytes acceptEncodingMB = request.getMimeHeaders().getValue("accept-encoding"); + if ((acceptEncodingMB == null) || (acceptEncodingMB.indexOf("gzip") == -1)) { + return false; + } + + // If force mode, the browser checks are skipped + if (compressionLevel != 2) { + // Check for incompatible Browser + Pattern noCompressionUserAgents = this.noCompressionUserAgents; + if (noCompressionUserAgents != null) { + MessageBytes userAgentValueMB = request.getMimeHeaders().getValue("user-agent"); + if(userAgentValueMB != null) { + String userAgentValue = userAgentValueMB.toString(); + if (noCompressionUserAgents.matcher(userAgentValue).matches()) { + return false; + } + } + } + } + + // All checks have passed. Compression is enabled. + + // Compressed content length is unknown so mark it as such. + response.setContentLength(-1); + // Configure the content encoding for compressed content + responseHeaders.setValue("Content-Encoding").setString("gzip"); + + return true; + } + + + /** + * Checks if any entry in the string array starts with the specified value + * + * @param sArray the StringArray + * @param value string + */ + private static boolean startsWithStringArray(String sArray[], String value) { + if (value == null) { + return false; + } + for (int i = 0; i < sArray.length; i++) { + if (value.startsWith(sArray[i])) { + return true; + } + } + return false; + } +} Propchange: tomcat/trunk/java/org/apache/coyote/CompressionConfig.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java?rev=1816549&r1=1816548&r2=1816549&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java (original) +++ tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java Tue Nov 28 14:22:17 2017 @@ -24,14 +24,16 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; import javax.servlet.http.HttpUpgradeHandler; import org.apache.coyote.AbstractProtocol; +import org.apache.coyote.CompressionConfig; import org.apache.coyote.Processor; +import org.apache.coyote.Request; +import org.apache.coyote.Response; import org.apache.coyote.UpgradeProtocol; import org.apache.coyote.UpgradeToken; import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler; @@ -48,6 +50,8 @@ public abstract class AbstractHttp11Prot protected static final StringManager sm = StringManager.getManager(AbstractHttp11Protocol.class); + private final CompressionConfig compressionConfig = new CompressionConfig(); + public AbstractHttp11Protocol(AbstractEndpoint<S,?> endpoint) { super(endpoint); @@ -207,127 +211,49 @@ public abstract class AbstractHttp11Prot } - private int compressionLevel = 0; - /** - * Set compression level. - * - * @param compression One of <code>on</code>, <code>force</code>, - * <code>off</code> or the minimum compression size in - * bytes which implies <code>on</code> - */ public void setCompression(String compression) { - if (compression.equals("on")) { - this.compressionLevel = 1; - } else if (compression.equals("force")) { - this.compressionLevel = 2; - } else if (compression.equals("off")) { - this.compressionLevel = 0; - } else { - try { - // Try to parse compression as an int, which would give the - // minimum compression size - setCompressionMinSize(Integer.parseInt(compression)); - this.compressionLevel = 1; - } catch (Exception e) { - this.compressionLevel = 0; - } - } + compressionConfig.setCompression(compression); } - - - /** - * Return compression level. - * - * @return The current compression level in string form (off/on/force) - */ public String getCompression() { - switch (compressionLevel) { - case 0: - return "off"; - case 1: - return "on"; - case 2: - return "force"; - } - return "off"; + return compressionConfig.getCompression(); } protected int getCompressionLevel() { - return compressionLevel; + return compressionConfig.getCompressionLevel(); } - private Pattern noCompressionUserAgents = null; - /** - * Obtain the String form of the regular expression that defines the user - * agents to not use gzip with. - * - * @return The regular expression as a String - */ public String getNoCompressionUserAgents() { - if (noCompressionUserAgents == null) { - return null; - } else { - return noCompressionUserAgents.toString(); - } + return compressionConfig.getNoCompressionUserAgents(); } protected Pattern getNoCompressionUserAgentsPattern() { - return noCompressionUserAgents; + return compressionConfig.getNoCompressionUserAgentsPattern(); } - /** - * Set no compression user agent pattern. Regular expression as supported - * by {@link Pattern}. e.g.: <code>gorilla|desesplorer|tigrus</code>. - * - * @param noCompressionUserAgents The regular expression for user agent - * strings for which compression should not - * be applied - */ public void setNoCompressionUserAgents(String noCompressionUserAgents) { - if (noCompressionUserAgents == null || noCompressionUserAgents.length() == 0) { - this.noCompressionUserAgents = null; - } else { - this.noCompressionUserAgents = - Pattern.compile(noCompressionUserAgents); - } + compressionConfig.setNoCompressionUserAgents(noCompressionUserAgents); } - private String compressibleMimeType = "text/html,text/xml,text/plain,text/css," + - "text/javascript,application/javascript,application/json,application/xml"; - private String[] compressibleMimeTypes = null; - public String getCompressibleMimeType() { return compressibleMimeType; } + public String getCompressibleMimeType() { + return compressionConfig.getCompressibleMimeType(); + } public void setCompressibleMimeType(String valueS) { - compressibleMimeType = valueS; - compressibleMimeTypes = null; + compressionConfig.setCompressibleMimeType(valueS); } public String[] getCompressibleMimeTypes() { - String[] result = compressibleMimeTypes; - if (result != null) { - return result; - } - List<String> values = new ArrayList<>(); - StringTokenizer tokens = new StringTokenizer(compressibleMimeType, ","); - while (tokens.hasMoreTokens()) { - String token = tokens.nextToken().trim(); - if (token.length() > 0) { - values.add(token); - } - } - result = values.toArray(new String[values.size()]); - compressibleMimeTypes = result; - return result; + return compressionConfig.getCompressibleMimeTypes(); } - private int compressionMinSize = 2048; - public int getCompressionMinSize() { return compressionMinSize; } - /** - * Set Minimum size to trigger compression. - * - * @param compressionMinSize The minimum content length required for - * compression in bytes - */ + public int getCompressionMinSize() { + return compressionConfig.getCompressionMinSize(); + } public void setCompressionMinSize(int compressionMinSize) { - this.compressionMinSize = compressionMinSize; + compressionConfig.setCompressionMinSize(compressionMinSize); + } + + + public boolean useCompression(Request request, Response response) { + return compressionConfig.useCompression(request, response); } Modified: tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java?rev=1816549&r1=1816548&r2=1816549&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java (original) +++ tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java Tue Nov 28 14:22:17 2017 @@ -190,90 +190,6 @@ public class Http11Processor extends Abs /** - * Checks if any entry in the string array starts with the specified value - * - * @param sArray the StringArray - * @param value string - */ - private static boolean startsWithStringArray(String sArray[], String value) { - if (value == null) { - return false; - } - for (int i = 0; i < sArray.length; i++) { - if (value.startsWith(sArray[i])) { - return true; - } - } - return false; - } - - - /** - * Check if the resource could be compressed, if the client supports it. - */ - private boolean isCompressible() { - - // Check if content is not already gzipped - MessageBytes contentEncodingMB = response.getMimeHeaders().getValue("Content-Encoding"); - - if ((contentEncodingMB != null) && (contentEncodingMB.indexOf("gzip") != -1)) { - return false; - } - - // If force mode, always compress (test purposes only) - if (protocol.getCompressionLevel() == 2) { - return true; - } - - // Check if sufficient length to trigger the compression - long contentLength = response.getContentLengthLong(); - if ((contentLength == -1) || (contentLength > protocol.getCompressionMinSize())) { - // Check for compatible MIME-TYPE - String[] compressibleMimeTypes = protocol.getCompressibleMimeTypes(); - if (compressibleMimeTypes != null) { - return startsWithStringArray(compressibleMimeTypes, response.getContentType()); - } - } - - return false; - } - - - /** - * Check if compression should be used for this resource. Already checked - * that the resource could be compressed if the client supports it. - */ - private boolean useCompression() { - - // Check if browser support gzip encoding - MessageBytes acceptEncodingMB = request.getMimeHeaders().getValue("accept-encoding"); - - if ((acceptEncodingMB == null) || (acceptEncodingMB.indexOf("gzip") == -1)) { - return false; - } - - // If force mode, always compress (test purposes only) - if (protocol.getCompressionLevel() == 2) { - return true; - } - - // Check for incompatible Browser - Pattern noCompressionUserAgents = protocol.getNoCompressionUserAgentsPattern(); - if (noCompressionUserAgents != null) { - MessageBytes userAgentValueMB = request.getMimeHeaders().getValue("user-agent"); - if(userAgentValueMB != null) { - String userAgentValue = userAgentValueMB.toString(); - if (noCompressionUserAgents.matcher(userAgentValue).matches()) { - return false; - } - } - } - - return true; - } - - - /** * Specialized utility method: find a sequence of lower case bytes inside * a ByteChunk. */ @@ -919,17 +835,10 @@ public class Http11Processor extends Abs } // Check for compression - boolean isCompressible = false; + boolean useCompression = false; - if (entityBody && (protocol.getCompressionLevel() > 0) && sendfileData == null) { - isCompressible = isCompressible(); - if (isCompressible) { - useCompression = useCompression(); - } - // Change content-length to -1 to force chunking - if (useCompression) { - response.setContentLength(-1); - } + if (entityBody && sendfileData == null) { + useCompression = protocol.useCompression(request, response); } MimeHeaders headers = response.getMimeHeaders(); @@ -972,22 +881,6 @@ public class Http11Processor extends Abs if (useCompression) { outputBuffer.addActiveFilter(outputFilters[Constants.GZIP_FILTER]); - headers.setValue("Content-Encoding").setString("gzip"); - } - // If it might be compressed, set the Vary header - if (isCompressible) { - // Make Proxies happy via Vary (from mod_deflate) - MessageBytes vary = headers.getValue("Vary"); - if (vary == null) { - // Add a new Vary header - headers.setValue("Vary").setString("Accept-Encoding"); - } else if (vary.equals("*")) { - // No action required - } else { - // Merge into current header - headers.setValue("Vary").setString( - vary.getString() + ",Accept-Encoding"); - } } // Add date header unless application has already set one (e.g. in a --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org