Author: markt Date: Thu Apr 16 19:31:57 2009 New Revision: 765727 URL: http://svn.apache.org/viewvc?rev=765727&view=rev Log: Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=46538 ETag must vary between compressed and uncompressed versions. Based on a patch by Oliver Schoett
Modified: tomcat/trunk/java/org/apache/coyote/http11/Constants.java tomcat/trunk/java/org/apache/coyote/http11/Http11AprProcessor.java tomcat/trunk/java/org/apache/coyote/http11/Http11NioProcessor.java tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java Modified: tomcat/trunk/java/org/apache/coyote/http11/Constants.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/Constants.java?rev=765727&r1=765726&r2=765727&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/Constants.java (original) +++ tomcat/trunk/java/org/apache/coyote/http11/Constants.java Thu Apr 16 19:31:57 2009 @@ -148,7 +148,10 @@ ByteChunk.convertToBytes("400"); public static final byte[] _404_BYTES = ByteChunk.convertToBytes("404"); - + public static final String VARY = "Vary"; + public static final String VARY_UNSPECIFIED = "*"; + public static final String ACCEPT_ENCODING = "Accept-Encoding"; + public static final String ETAG = "ETag"; /** * Identity filters (input and output). Modified: tomcat/trunk/java/org/apache/coyote/http11/Http11AprProcessor.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/Http11AprProcessor.java?rev=765727&r1=765726&r2=765727&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/Http11AprProcessor.java (original) +++ tomcat/trunk/java/org/apache/coyote/http11/Http11AprProcessor.java Thu Apr 16 19:31:57 2009 @@ -1486,16 +1486,9 @@ /** - * Check for compression + * Check if browser allows compression */ - private boolean isCompressable() { - - // Nope Compression could works in HTTP 1.0 also - // cf: mod_deflate - - // Compression only since HTTP 1.1 - // if (! http11) - // return false; + private boolean isCompressableBrowser() { // Check if browser support gzip encoding MessageBytes acceptEncodingMB = @@ -1505,15 +1498,7 @@ || (acceptEncodingMB.indexOf("gzip") == -1)) return false; - // Check if content is not allready gzipped - MessageBytes contentEncodingMB = - response.getMimeHeaders().getValue("Content-Encoding"); - - if ((contentEncodingMB != null) - && (contentEncodingMB.indexOf("gzip") != -1)) - return false; - - // If force mode, allways compress (test purposes only) + // If force mode, always compress (test purposes only) if (compressionLevel == 2) return true; @@ -1530,8 +1515,23 @@ return false; } } + return true; + } + + /* + * Check if response allows compression + */ + private boolean isCompressableResponse() { + + // Check if content is not already gzipped + MessageBytes contentEncodingMB = + response.getMimeHeaders().getValue("Content-Encoding"); - // Check if suffisant len to trig the compression + if ((contentEncodingMB != null) + && (contentEncodingMB.indexOf("gzip") != -1)) + return false; + + // Check if sufficient length to trigger the compression long contentLength = response.getContentLengthLong(); if ((contentLength == -1) || (contentLength > compressionMinSize)) { @@ -1598,18 +1598,35 @@ ((Long) request.getAttribute("org.apache.tomcat.sendfile.end")).longValue(); } } - + + MimeHeaders headers = response.getMimeHeaders(); + // Check for compression boolean useCompression = false; if (entityBody && (compressionLevel > 0) && (sendfileData == null)) { - useCompression = isCompressable(); + if (isCompressableResponse()) { + // Always send the Vary header when response could be compressed + MessageBytes varyHeader = headers.getValue(Constants.VARY); + if (varyHeader == null) { + headers.addValue(Constants.VARY).setString( + Constants.ACCEPT_ENCODING); + } else { + if (varyHeader.indexOf(Constants.ACCEPT_ENCODING) == -1 && + !varyHeader.equals(Constants.VARY_UNSPECIFIED)) { + varyHeader.setString(varyHeader.toString() + "," + + Constants.ACCEPT_ENCODING); + } + } + } + + useCompression = isCompressableBrowser(); + // Change content-length to -1 to force chunking if (useCompression) { response.setContentLength(-1); } } - MimeHeaders headers = response.getMimeHeaders(); if (!entityBody) { response.setContentLength(-1); } else { @@ -1645,8 +1662,22 @@ if (useCompression) { outputBuffer.addActiveFilter(outputFilters[Constants.GZIP_FILTER]); headers.setValue("Content-Encoding").setString("gzip"); - // Make Proxies happy via Vary (from mod_deflate) - headers.setValue("Vary").setString("Accept-Encoding"); + + // Ensure eTag for compressed content is different to eTag for + // uncompressed content + MessageBytes eTagHeader = headers.getValue(Constants.ETAG); + if (eTagHeader != null) { + String eTag = eTagHeader.toString(); + int len = eTag.length(); + if (len > 1 && eTag.charAt(len - 1) == '"') { + // Add compression marker before closing quote + eTag = eTag.substring(0, len -1) + "-gz\""; + } else { + // Unquoted ETag - shouldn't happen - TODO complain + eTag = eTag + "-gz"; + } + eTagHeader.setString(eTag); + } } // Add date header Modified: tomcat/trunk/java/org/apache/coyote/http11/Http11NioProcessor.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/Http11NioProcessor.java?rev=765727&r1=765726&r2=765727&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/Http11NioProcessor.java (original) +++ tomcat/trunk/java/org/apache/coyote/http11/Http11NioProcessor.java Thu Apr 16 19:31:57 2009 @@ -1529,16 +1529,9 @@ /** - * Check for compression + * Check if browser allows compression */ - private boolean isCompressable() { - - // Nope Compression could works in HTTP 1.0 also - // cf: mod_deflate - - // Compression only since HTTP 1.1 - // if (! http11) - // return false; + private boolean isCompressableBrowser() { // Check if browser support gzip encoding MessageBytes acceptEncodingMB = @@ -1548,15 +1541,7 @@ || (acceptEncodingMB.indexOf("gzip") == -1)) return false; - // Check if content is not allready gzipped - MessageBytes contentEncodingMB = - response.getMimeHeaders().getValue("Content-Encoding"); - - if ((contentEncodingMB != null) - && (contentEncodingMB.indexOf("gzip") != -1)) - return false; - - // If force mode, allways compress (test purposes only) + // If force mode, always compress (test purposes only) if (compressionLevel == 2) return true; @@ -1573,8 +1558,23 @@ return false; } } + return true; + } + + /* + * Check if response allows compression + */ + private boolean isCompressableResponse() { + + // Check if content is not already gzipped + MessageBytes contentEncodingMB = + response.getMimeHeaders().getValue("Content-Encoding"); - // Check if suffisant len to trig the compression + if ((contentEncodingMB != null) + && (contentEncodingMB.indexOf("gzip") != -1)) + return false; + + // Check if sufficient length to trigger the compression long contentLength = response.getContentLengthLong(); if ((contentLength == -1) || (contentLength > compressionMinSize)) { @@ -1639,19 +1639,33 @@ } } - + MimeHeaders headers = response.getMimeHeaders(); // Check for compression boolean useCompression = false; if (entityBody && (compressionLevel > 0) && (sendfileData == null)) { - useCompression = isCompressable(); + if (isCompressableResponse()) { + // Always send the Vary header when response could be compressed + MessageBytes varyHeader = headers.getValue(Constants.VARY); + if (varyHeader == null) { + headers.addValue(Constants.VARY).setString( + Constants.ACCEPT_ENCODING); + } else { + if (varyHeader.indexOf(Constants.ACCEPT_ENCODING) == -1 && + !varyHeader.equals(Constants.VARY_UNSPECIFIED)) { + varyHeader.setString(varyHeader.toString() + "," + + Constants.ACCEPT_ENCODING); + } + } + } + useCompression = isCompressableBrowser(); + // Change content-length to -1 to force chunking if (useCompression) { response.setContentLength(-1); } } - MimeHeaders headers = response.getMimeHeaders(); if (!entityBody) { response.setContentLength(-1); } else { @@ -1687,8 +1701,22 @@ if (useCompression) { outputBuffer.addActiveFilter(outputFilters[Constants.GZIP_FILTER]); headers.setValue("Content-Encoding").setString("gzip"); - // Make Proxies happy via Vary (from mod_deflate) - headers.setValue("Vary").setString("Accept-Encoding"); + + // Ensure eTag for compressed content is different to eTag for + // uncompressed content + MessageBytes eTagHeader = headers.getValue(Constants.ETAG); + if (eTagHeader != null) { + String eTag = eTagHeader.toString(); + int len = eTag.length(); + if (len > 1 && eTag.charAt(len - 1) == '"') { + // Add compression marker before closing quote + eTag = eTag.substring(0, len -1) + "-gz\""; + } else { + // Unquoted ETag - shouldn't happen - TODO complain + eTag = eTag + "-gz"; + } + eTagHeader.setString(eTag); + } } // Add date header 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=765727&r1=765726&r2=765727&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java (original) +++ tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java Thu Apr 16 19:31:57 2009 @@ -1399,16 +1399,9 @@ /** - * Check for compression + * Check if browser allows compression */ - private boolean isCompressable() { - - // Nope Compression could works in HTTP 1.0 also - // cf: mod_deflate - - // Compression only since HTTP 1.1 - // if (! http11) - // return false; + private boolean isCompressableBrowser() { // Check if browser support gzip encoding MessageBytes acceptEncodingMB = @@ -1418,15 +1411,7 @@ || (acceptEncodingMB.indexOf("gzip") == -1)) return false; - // Check if content is not allready gzipped - MessageBytes contentEncodingMB = - response.getMimeHeaders().getValue("Content-Encoding"); - - if ((contentEncodingMB != null) - && (contentEncodingMB.indexOf("gzip") != -1)) - return false; - - // If force mode, allways compress (test purposes only) + // If force mode, always compress (test purposes only) if (compressionLevel == 2) return true; @@ -1443,8 +1428,23 @@ return false; } } + return true; + } + + /* + * Check if response allows compression + */ + private boolean isCompressableResponse() { + + // Check if content is not already gzipped + MessageBytes contentEncodingMB = + response.getMimeHeaders().getValue("Content-Encoding"); - // Check if suffisant len to trig the compression + if ((contentEncodingMB != null) + && (contentEncodingMB.indexOf("gzip") != -1)) + return false; + + // Check if sufficient length to trigger the compression long contentLength = response.getContentLengthLong(); if ((contentLength == -1) || (contentLength > compressionMinSize)) { @@ -1495,18 +1495,34 @@ contentDelimitation = true; } + MimeHeaders headers = response.getMimeHeaders(); + // Check for compression boolean useCompression = false; if (entityBody && (compressionLevel > 0)) { - useCompression = isCompressable(); - + if (isCompressableResponse()) { + // Always send the Vary header when response could be compressed + MessageBytes varyHeader = headers.getValue(Constants.VARY); + if (varyHeader == null) { + headers.addValue(Constants.VARY).setString( + Constants.ACCEPT_ENCODING); + } else { + if (varyHeader.indexOf(Constants.ACCEPT_ENCODING) == -1 && + !varyHeader.equals(Constants.VARY_UNSPECIFIED)) { + varyHeader.setString(varyHeader.toString() + "," + + Constants.ACCEPT_ENCODING); + } + } + } + + useCompression = isCompressableBrowser(); + // Change content-length to -1 to force chunking if (useCompression) { response.setContentLength(-1); } } - MimeHeaders headers = response.getMimeHeaders(); if (!entityBody) { response.setContentLength(-1); } else { @@ -1542,8 +1558,22 @@ if (useCompression) { outputBuffer.addActiveFilter(outputFilters[Constants.GZIP_FILTER]); headers.setValue("Content-Encoding").setString("gzip"); - // Make Proxies happy via Vary (from mod_deflate) - headers.setValue("Vary").setString("Accept-Encoding"); + + // Ensure eTag for compressed content is different to eTag for + // uncompressed content + MessageBytes eTagHeader = headers.getValue(Constants.ETAG); + if (eTagHeader != null) { + String eTag = eTagHeader.toString(); + int len = eTag.length(); + if (len > 1 && eTag.charAt(len - 1) == '"') { + // Add compression marker before closing quote + eTag = eTag.substring(0, len -1) + "-gz\""; + } else { + // Unquoted ETag - shouldn't happen - TODO complain + eTag = eTag + "-gz"; + } + eTagHeader.setString(eTag); + } } // Add date header --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org