Support for configurable set of precompressed static resources

* Support for configurable set of precompressed static resources

Signed-off-by: Mikko Tiihonen <mikko.tiihonen@nitorcreations.com>

* Use QuotedQualityCSV to parse preferred content encodings

Signed-off-by: Mikko Tiihonen <mikko.tiihonen@nitorcreations.com>
This commit is contained in:
Mikko Tiihonen 2016-04-07 06:57:00 +03:00 committed by Greg Wilkins
parent 21e4cfecfc
commit 3261e03edb
19 changed files with 471 additions and 158 deletions

View File

@ -0,0 +1,43 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
/* ------------------------------------------------------------ */
public class CompressedContentFormat
{
public static final CompressedContentFormat GZIP = new CompressedContentFormat("gzip", ".gz");
public static final CompressedContentFormat BR = new CompressedContentFormat("br", ".br");
public static final CompressedContentFormat[] NONE = new CompressedContentFormat[0];
public final String _encoding;
public final String _extension;
public final String _etag;
public final String _etagQuote;
public final PreEncodedHttpField _contentEncoding;
public CompressedContentFormat(String encoding, String extension)
{
_encoding = encoding;
_extension = extension;
_etag = "--" + encoding;
_etagQuote = _etag + "\"";
_contentEncoding = new PreEncodedHttpField(HttpHeader.CONTENT_ENCODING, encoding);
}
}

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.Map;
import org.eclipse.jetty.http.MimeTypes.Type;
import org.eclipse.jetty.util.resource.Resource;
@ -63,7 +64,7 @@ public interface HttpContent
ReadableByteChannel getReadableByteChannel() throws IOException;
void release();
HttpContent getGzipContent();
Map<CompressedContentFormat,? extends HttpContent> getPrecompressedContents();
public interface Factory

View File

@ -22,33 +22,26 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import org.eclipse.jetty.http.MimeTypes.Type;
import java.util.Map;
import org.eclipse.jetty.http.MimeTypes.Type;
import org.eclipse.jetty.util.resource.Resource;
/* ------------------------------------------------------------ */
public class GzipHttpContent implements HttpContent
public class PrecompressedHttpContent implements HttpContent
{
private final HttpContent _content;
private final HttpContent _contentGz;
public final static String ETAG_GZIP="--gzip";
public final static String ETAG_GZIP_QUOTE="--gzip\"";
public final static PreEncodedHttpField CONTENT_ENCODING_GZIP=new PreEncodedHttpField(HttpHeader.CONTENT_ENCODING,"gzip");
public static String removeGzipFromETag(String etag)
private final HttpContent _content;
private final HttpContent _precompressedContent;
private final CompressedContentFormat _format;
public PrecompressedHttpContent(HttpContent content, HttpContent precompressedContent, CompressedContentFormat format)
{
if (etag==null)
return null;
int i = etag.indexOf(ETAG_GZIP_QUOTE);
if (i<0)
return etag;
return etag.substring(0,i)+'"';
}
public GzipHttpContent(HttpContent content, HttpContent contentGz)
{
_content=content;
_contentGz=contentGz;
_precompressedContent=precompressedContent;
_format=format;
if (_precompressedContent == null || _format == null) {
throw new NullPointerException("Missing compressed content and/or format");
}
}
@Override
@ -78,7 +71,7 @@ public class GzipHttpContent implements HttpContent
@Override
public String getETagValue()
{
return _content.getResource().getWeakETag(ETAG_GZIP);
return _content.getResource().getWeakETag(_format._etag);
}
@Override
@ -108,13 +101,13 @@ public class GzipHttpContent implements HttpContent
@Override
public HttpField getContentEncoding()
{
return CONTENT_ENCODING_GZIP;
return _format._contentEncoding;
}
@Override
public String getContentEncodingValue()
{
return CONTENT_ENCODING_GZIP.getValue();
return _format._contentEncoding.getValue();
}
@Override
@ -138,50 +131,50 @@ public class GzipHttpContent implements HttpContent
@Override
public ByteBuffer getIndirectBuffer()
{
return _contentGz.getIndirectBuffer();
return _precompressedContent.getIndirectBuffer();
}
@Override
public ByteBuffer getDirectBuffer()
{
return _contentGz.getDirectBuffer();
return _precompressedContent.getDirectBuffer();
}
@Override
public HttpField getContentLength()
{
return _contentGz.getContentLength();
return _precompressedContent.getContentLength();
}
@Override
public long getContentLengthValue()
{
return _contentGz.getContentLengthValue();
return _precompressedContent.getContentLengthValue();
}
@Override
public InputStream getInputStream() throws IOException
{
return _contentGz.getInputStream();
return _precompressedContent.getInputStream();
}
@Override
public ReadableByteChannel getReadableByteChannel() throws IOException
{
return _contentGz.getReadableByteChannel();
return _precompressedContent.getReadableByteChannel();
}
@Override
public String toString()
{
return String.format("GzipHttpContent@%x{r=%s|%s,lm=%s|%s,ct=%s}",hashCode(),
_content.getResource(),_contentGz.getResource(),
_content.getResource().lastModified(),_contentGz.getResource().lastModified(),
return String.format("PrecompressedHttpContent@%x{e=%s,r=%s|%s,lm=%s|%s,ct=%s}",hashCode(),_format._encoding,
_content.getResource(),_precompressedContent.getResource(),
_content.getResource().lastModified(),_precompressedContent.getResource().lastModified(),
getContentType());
}
@Override
public HttpContent getGzipContent()
public Map<CompressedContentFormat, HttpContent> getPrecompressedContents()
{
return null;
}

View File

@ -22,6 +22,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.http.MimeTypes.Type;
import org.eclipse.jetty.util.BufferUtil;
@ -39,7 +41,7 @@ public class ResourceHttpContent implements HttpContent
final Resource _resource;
final String _contentType;
final int _maxBuffer;
HttpContent _gzip;
Map<CompressedContentFormat, HttpContent> _precompressedContents;
String _etag;
/* ------------------------------------------------------------ */
@ -55,12 +57,19 @@ public class ResourceHttpContent implements HttpContent
}
/* ------------------------------------------------------------ */
public ResourceHttpContent(final Resource resource, final String contentType, int maxBuffer, HttpContent gzip)
public ResourceHttpContent(final Resource resource, final String contentType, int maxBuffer, Map<CompressedContentFormat, HttpContent> precompressedContents)
{
_resource=resource;
_contentType=contentType;
_maxBuffer=maxBuffer;
_gzip=gzip;
if (precompressedContents == null) {
_precompressedContents = null;
} else {
_precompressedContents = new HashMap<>(precompressedContents.size());
for (Map.Entry<CompressedContentFormat, HttpContent> entry : precompressedContents.entrySet()) {
_precompressedContents.put(entry.getKey(), new PrecompressedHttpContent(this, entry.getValue(), entry.getKey()));
}
}
}
/* ------------------------------------------------------------ */
@ -214,14 +223,14 @@ public class ResourceHttpContent implements HttpContent
@Override
public String toString()
{
return String.format("%s@%x{r=%s,gz=%b}",this.getClass().getSimpleName(),hashCode(),_resource,_gzip!=null);
return String.format("%s@%x{r=%s,c=%b}",this.getClass().getSimpleName(),hashCode(),_resource,_precompressedContents!=null);
}
/* ------------------------------------------------------------ */
@Override
public HttpContent getGzipContent()
public Map<CompressedContentFormat, HttpContent> getPrecompressedContents()
{
return _gzip==null?null:new GzipHttpContent(this,_gzip);
return _precompressedContents;
}
}

View File

@ -11,6 +11,7 @@ avi=video/x-msvideo
bcpio=application/x-bcpio
bin=application/octet-stream
bmp=image/bmp
br=application/brotli
cab=application/x-cabinet
cdf=application/x-netcdf
chm=application/vnd.ms-htmlhelp
@ -185,5 +186,6 @@ xslt=application/xslt+xml
xul=application/vnd.mozilla.xul+xml
xwd=image/x-xwindowdump
xyz=chemical/x-xyz
xz=application/x-xz
z=application/compress
zip=application/zip

View File

@ -22,9 +22,10 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
@ -32,14 +33,15 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.http.GzipHttpContent;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.MimeTypes.Type;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http.PrecompressedHttpContent;
import org.eclipse.jetty.http.ResourceHttpContent;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
@ -59,7 +61,7 @@ public class ResourceCache implements HttpContent.Factory
private final ResourceCache _parent;
private final MimeTypes _mimeTypes;
private final boolean _etags;
private final boolean _gzip;
private final CompressedContentFormat[] _precompressedFormats;
private final boolean _useFileMappedBuffer;
private int _maxCachedFileSize =128*1024*1024;
@ -73,9 +75,9 @@ public class ResourceCache implements HttpContent.Factory
* @param mimeTypes Mimetype to use for meta data
* @param useFileMappedBuffer true to file memory mapped buffers
* @param etags true to support etags
* @param gzip true to support gzip
* @param precompressedFormats array of precompression formats to support
*/
public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags,boolean gzip)
public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags,CompressedContentFormat[] precompressedFormats)
{
_factory = factory;
_cache=new ConcurrentHashMap<String,CachedHttpContent>();
@ -85,7 +87,7 @@ public class ResourceCache implements HttpContent.Factory
_parent=parent;
_useFileMappedBuffer=useFileMappedBuffer;
_etags=etags;
_gzip=gzip;
_precompressedFormats=precompressedFormats;
}
/* ------------------------------------------------------------ */
@ -246,27 +248,29 @@ public class ResourceCache implements HttpContent.Factory
{
CachedHttpContent content=null;
// Look for a gzip resource
if (_gzip)
// Look for precompressed resources
if (_precompressedFormats.length > 0)
{
String pathInContextGz=pathInContext+".gz";
CachedHttpContent contentGz = _cache.get(pathInContextGz);
if (contentGz==null || !contentGz.isValid())
{
contentGz=null;
Resource resourceGz=_factory.getResource(pathInContextGz);
if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length())
{
contentGz = new CachedHttpContent(pathInContextGz,resourceGz,null);
CachedHttpContent added = _cache.putIfAbsent(pathInContextGz,contentGz);
if (added!=null)
{
contentGz.invalidate();
contentGz=added;
Map<CompressedContentFormat, CachedHttpContent> precompresssedContents=new HashMap<>(_precompressedFormats.length);
for (CompressedContentFormat format : _precompressedFormats) {
String compressedPathInContext=pathInContext+format._extension;
CachedHttpContent compressedContent=_cache.get(compressedPathInContext);
if (compressedContent==null || compressedContent.isValid()) {
compressedContent=null;
Resource compressedResource = _factory.getResource(compressedPathInContext);
if (compressedResource.exists() && compressedResource.lastModified()>=resource.lastModified() && compressedResource.length()<resource.length()) {
compressedContent=new CachedHttpContent(compressedPathInContext,compressedResource,null);
CachedHttpContent added=_cache.putIfAbsent(compressedPathInContext,compressedContent);
if (added!=null) {
compressedContent.invalidate();
compressedContent=added;
}
}
}
if (compressedContent!=null)
precompresssedContents.put(format,compressedContent);
}
content = new CachedHttpContent(pathInContext,resource,contentGz);
content = new CachedHttpContent(pathInContext,resource,precompresssedContents);
}
else
content = new CachedHttpContent(pathInContext,resource,null);
@ -282,23 +286,26 @@ public class ResourceCache implements HttpContent.Factory
return content;
}
// Look for non Cacheable gzip resource or content
// Look for non Cacheable precompressed resource or content
String mt = _mimeTypes.getMimeByExtension(pathInContext);
if (_gzip)
{
// Is the gzip content cached?
String pathInContextGz=pathInContext+".gz";
CachedHttpContent contentGz = _cache.get(pathInContextGz);
if (contentGz!=null && contentGz.isValid() && contentGz.getResource().lastModified()>=resource.lastModified())
return new ResourceHttpContent(resource,mt,maxBufferSize,contentGz);
// Is there a gzip resource?
Resource resourceGz=_factory.getResource(pathInContextGz);
if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length())
return new ResourceHttpContent(resource,mt,maxBufferSize,
new ResourceHttpContent(resourceGz,_mimeTypes.getMimeByExtension(pathInContextGz),maxBufferSize));
if (_precompressedFormats.length>0) {
// Is the precompressed content cached?
Map<CompressedContentFormat, HttpContent> compressedContents = new HashMap<>();
for (CompressedContentFormat format : _precompressedFormats) {
String compressedPathInContext=pathInContext+format._extension;
CachedHttpContent compressedContent=_cache.get(compressedPathInContext);
if (compressedContent!=null && compressedContent.isValid() && compressedContent.getResource().lastModified()>=resource.lastModified())
compressedContents.put(format,compressedContent);
// Is there a precompressed resource?
Resource compressedResource=_factory.getResource(compressedPathInContext);
if (compressedResource.exists() && compressedResource.lastModified()>=resource.lastModified() && compressedResource.length()<resource.length())
compressedContents.put(format,new ResourceHttpContent(compressedResource,_mimeTypes.getMimeByExtension(compressedPathInContext),maxBufferSize));
}
if (!compressedContents.isEmpty())
return new ResourceHttpContent(resource, mt, maxBufferSize, compressedContents);
}
return new ResourceHttpContent(resource,mt,maxBufferSize);
}
@ -396,14 +403,14 @@ public class ResourceCache implements HttpContent.Factory
final HttpField _lastModified;
final long _lastModifiedValue;
final HttpField _etag;
final CachedGzipHttpContent _gzipped;
final Map<CompressedContentFormat, CachedPrecompressedHttpContent> _precompressed;
volatile long _lastAccessed;
AtomicReference<ByteBuffer> _indirectBuffer=new AtomicReference<ByteBuffer>();
AtomicReference<ByteBuffer> _directBuffer=new AtomicReference<ByteBuffer>();
/* ------------------------------------------------------------ */
CachedHttpContent(String pathInContext,Resource resource,CachedHttpContent gzipped)
CachedHttpContent(String pathInContext,Resource resource,Map<CompressedContentFormat, CachedHttpContent> precompressedResources)
{
_key=pathInContext;
_resource=resource;
@ -427,8 +434,15 @@ public class ResourceCache implements HttpContent.Factory
_lastAccessed=System.currentTimeMillis();
_etag=ResourceCache.this._etags?new PreEncodedHttpField(HttpHeader.ETAG,resource.getWeakETag()):null;
_gzipped=gzipped==null?null:new CachedGzipHttpContent(this,gzipped);
if (precompressedResources != null) {
_precompressed = new HashMap<>(precompressedResources.size());
for (Map.Entry<CompressedContentFormat, CachedHttpContent> entry : precompressedResources.entrySet()) {
_precompressed.put(entry.getKey(), new CachedPrecompressedHttpContent(this, entry.getValue(), entry.getKey()));
}
} else {
_precompressed = null;
}
}
@ -650,38 +664,49 @@ public class ResourceCache implements HttpContent.Factory
@Override
public String toString()
{
return String.format("CachedContent@%x{r=%s,e=%b,lm=%s,ct=%s,gz=%b}",hashCode(),_resource,_resource.exists(),_lastModified,_contentType,_gzipped!=null);
return String.format("CachedContent@%x{r=%s,e=%b,lm=%s,ct=%s,c=%b}",hashCode(),_resource,_resource.exists(),_lastModified,_contentType,!_precompressed.isEmpty());
}
/* ------------------------------------------------------------ */
@Override
public HttpContent getGzipContent()
public Map<CompressedContentFormat,? extends HttpContent> getPrecompressedContents()
{
return (_gzipped!=null && _gzipped.isValid())?_gzipped:null;
if (_precompressed==null)
return null;
Map<CompressedContentFormat, CachedPrecompressedHttpContent> ret=_precompressed;
for (Map.Entry<CompressedContentFormat, CachedPrecompressedHttpContent> entry:_precompressed.entrySet())
{
if (!entry.getValue().isValid()) {
if (ret == _precompressed)
ret = new HashMap<>(_precompressed);
ret.remove(entry.getKey());
}
}
return ret;
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
public class CachedGzipHttpContent extends GzipHttpContent
public class CachedPrecompressedHttpContent extends PrecompressedHttpContent
{
private final CachedHttpContent _content;
private final CachedHttpContent _contentGz;
private final CachedHttpContent _precompressedContent;
private final HttpField _etag;
CachedGzipHttpContent(CachedHttpContent content, CachedHttpContent contentGz)
CachedPrecompressedHttpContent(CachedHttpContent content, CachedHttpContent precompressedContent, CompressedContentFormat format)
{
super(content,contentGz);
super(content,precompressedContent,format);
_content=content;
_contentGz=contentGz;
_precompressedContent=precompressedContent;
_etag=(ResourceCache.this._etags)?new PreEncodedHttpField(HttpHeader.ETAG,_content.getResource().getWeakETag("--gzip")):null;
_etag=(ResourceCache.this._etags)?new PreEncodedHttpField(HttpHeader.ETAG,_content.getResource().getWeakETag(format._etag)):null;
}
public boolean isValid()
{
return _contentGz.isValid() && _content.isValid() && _content.getResource().lastModified() <= _contentGz.getResource().lastModified();
return _precompressedContent.isValid() && _content.isValid() && _content.getResource().lastModified() <= _precompressedContent.getResource().lastModified();
}
@Override

View File

@ -19,7 +19,10 @@
package org.eclipse.jetty.server;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpContent.Factory;
import org.eclipse.jetty.http.MimeTypes;
@ -37,14 +40,14 @@ public class ResourceContentFactory implements Factory
{
private final ResourceFactory _factory;
private final MimeTypes _mimeTypes;
private final boolean _gzip;
private final CompressedContentFormat[] _precompressedFormats;
/* ------------------------------------------------------------ */
public ResourceContentFactory(ResourceFactory factory, MimeTypes mimeTypes, boolean gzip)
public ResourceContentFactory(ResourceFactory factory, MimeTypes mimeTypes, CompressedContentFormat[] precompressedFormats)
{
_factory=factory;
_mimeTypes=mimeTypes;
_gzip=gzip;
_precompressedFormats=precompressedFormats;
}
/* ------------------------------------------------------------ */
@ -69,18 +72,20 @@ public class ResourceContentFactory implements Factory
if (resource.isDirectory())
return new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),maxBufferSize);
// Look for a gzip resource or content
// Look for a precompressed resource or content
String mt = _mimeTypes.getMimeByExtension(pathInContext);
if (_gzip)
{
// Is there a gzip resource?
String pathInContextGz=pathInContext+".gz";
Resource resourceGz=_factory.getResource(pathInContextGz);
if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length())
return new ResourceHttpContent(resource,mt,maxBufferSize,
new ResourceHttpContent(resourceGz,_mimeTypes.getMimeByExtension(pathInContextGz),maxBufferSize));
if (_precompressedFormats.length>0) {
// Is there a compressed resource?
Map<CompressedContentFormat,HttpContent> compressedContents = new HashMap<>(_precompressedFormats.length);
for (CompressedContentFormat format:_precompressedFormats) {
String compressedPathInContext=pathInContext+format._extension;
Resource compressedResource=_factory.getResource(compressedPathInContext);
if (compressedResource.exists() && compressedResource.lastModified()>=resource.lastModified() && compressedResource.length()<resource.length())
compressedContents.put(format, new ResourceHttpContent(compressedResource,_mimeTypes.getMimeByExtension(compressedPathInContext),maxBufferSize));
}
if (!compressedContents.isEmpty())
return new ResourceHttpContent(resource,mt,maxBufferSize,compressedContents);
}
return new ResourceHttpContent(resource,mt,maxBufferSize);
}

View File

@ -18,16 +18,20 @@
package org.eclipse.jetty.server;
import static org.eclipse.jetty.http.GzipHttpContent.ETAG_GZIP_QUOTE;
import static org.eclipse.jetty.http.GzipHttpContent.removeGzipFromETag;
import static org.eclipse.jetty.http.CompressedContentFormat.BR;
import static org.eclipse.jetty.http.CompressedContentFormat.GZIP;
import static org.eclipse.jetty.http.HttpFields.qualityList;
import static org.eclipse.jetty.http.HttpHeaderValue.IDENTITY;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import javax.servlet.AsyncContext;
import javax.servlet.RequestDispatcher;
@ -35,6 +39,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.DateParser;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpField;
@ -67,7 +72,7 @@ public abstract class ResourceService
private boolean _acceptRanges=true;
private boolean _dirAllowed=true;
private boolean _redirectWelcome=false;
private boolean _gzip=false;
private CompressedContentFormat[] _precompressedFormats=new CompressedContentFormat[0];
private boolean _pathInfoOnly=false;
private boolean _etags=false;
private HttpField _cacheControl;
@ -113,14 +118,14 @@ public abstract class ResourceService
_redirectWelcome = redirectWelcome;
}
public boolean isGzip()
public CompressedContentFormat[] getPrecompressedFormats()
{
return _gzip;
return _precompressedFormats;
}
public void setGzip(boolean gzip)
public void setPrecompressedFormats(CompressedContentFormat[] precompressedFormats)
{
_gzip = gzip;
_precompressedFormats = precompressedFormats;
}
public boolean isPathInfoOnly()
@ -195,7 +200,7 @@ public abstract class ResourceService
String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
boolean gzippable=_gzip && !endsWithSlash && !included && reqRanges==null;
boolean checkPrecompressedVariants=_precompressedFormats.length > 0 && !endsWithSlash && !included && reqRanges==null;
HttpContent content=null;
boolean release_content=true;
@ -237,20 +242,22 @@ public abstract class ResourceService
if (!included && !passConditionalHeaders(request,response,content))
return;
// Gzip?
HttpContent gzip_content = gzippable?content.getGzipContent():null;
if (gzip_content!=null)
// Precompressed variant available?
Map<CompressedContentFormat,? extends HttpContent> precompressedContents = checkPrecompressedVariants?content.getPrecompressedContents():null;
if (precompressedContents!=null)
{
// Tell caches that response may vary by accept-encoding
response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
// Does the client accept gzip?
String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
if (accept!=null && accept.indexOf("gzip")>=0)
List<String> preferredEncodings = HttpFields.qualityList(request.getHeaders(HttpHeader.ACCEPT_ENCODING.asString()));
CompressedContentFormat precompressedContentEncoding = getBestPrecompressedContent(preferredEncodings, precompressedContents.keySet());
if (precompressedContentEncoding!=null)
{
HttpContent precompressedContent = precompressedContents.get(precompressedContentEncoding);
if (LOG.isDebugEnabled())
LOG.debug("gzip={}",gzip_content);
content=gzip_content;
LOG.debug("precompressed={}",precompressedContent);
content=precompressedContent;
response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),precompressedContentEncoding._encoding);
}
}
@ -278,6 +285,25 @@ public abstract class ResourceService
}
}
private CompressedContentFormat getBestPrecompressedContent(List<String> preferredEncodings, Collection<CompressedContentFormat> availableFormats) {
if (availableFormats.isEmpty())
return null;
for (String encoding : preferredEncodings)
{
for (CompressedContentFormat format : availableFormats)
if (format._encoding.equals(encoding))
return format;
if ("*".equals(encoding))
return availableFormats.iterator().next();
if (IDENTITY.asString().equals(encoding))
return null;
}
return null;
}
protected void sendWelcome(HttpContent content, String pathInContext, boolean endsWithSlash, boolean included, HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
@ -346,9 +372,9 @@ public abstract class ResourceService
/* ------------------------------------------------------------ */
protected boolean isGzippedContent(String path)
{
if (path == null || _gzipEquivalentFileExtensions==null)
if (path == null || _gzipEquivalentFileExtensions==null)
return false;
for (String suffix:_gzipEquivalentFileExtensions)
if (path.endsWith(suffix))
return true;
@ -433,7 +459,7 @@ public abstract class ResourceService
QuotedCSV quoted = new QuotedCSV(true,ifm);
for (String tag : quoted)
{
if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
if (tagEquals(etag, tag))
{
match=true;
break;
@ -451,7 +477,7 @@ public abstract class ResourceService
if (ifnm!=null && etag!=null)
{
// Handle special case of exact match OR gzip exact match
if (etag.equals(ifnm) || ifnm.endsWith(ETAG_GZIP_QUOTE) && ifnm.indexOf(',')<0 && etag.equals(removeGzipFromETag(etag)))
if (tagEquals(etag, ifnm) && ifnm.indexOf(',')<0)
{
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.setHeader(HttpHeader.ETAG.asString(),ifnm);
@ -462,7 +488,7 @@ public abstract class ResourceService
QuotedCSV quoted = new QuotedCSV(true,ifnm);
for (String tag : quoted)
{
if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
if (tagEquals(etag, tag))
{
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.setHeader(HttpHeader.ETAG.asString(),tag);
@ -518,6 +544,20 @@ public abstract class ResourceService
return true;
}
protected boolean tagEquals(String etag, String tag)
{
if (etag.equals(tag))
return true;
if (tag.endsWith(GZIP._etagQuote)) {
int i = tag.indexOf(GZIP._etagQuote);
return etag.equals(tag.substring(0,i) + '"');
}
if (tag.endsWith(BR._etagQuote)) {
int i = tag.indexOf(BR._etagQuote);
return etag.equals(tag.substring(0,i) + '"');
}
return false;
}
/* ------------------------------------------------------------------- */
protected void sendDirectory(HttpServletRequest request,

View File

@ -27,7 +27,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.MimeTypes;
@ -103,7 +103,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory
_context = (scontext == null?null:scontext.getContextHandler());
_mimeTypes = _context == null?new MimeTypes():_context.getMimeTypes();
_resourceService.setContentFactory(new ResourceContentFactory(this,_mimeTypes,_resourceService.isGzip()));
_resourceService.setContentFactory(new ResourceContentFactory(this,_mimeTypes,_resourceService.getPrecompressedFormats()));
super.doStart();
}
@ -319,9 +319,23 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory
/**
* @return If set to true, then static content will be served as gzip content encoded if a matching resource is found ending with ".gz"
*/
@Deprecated
public boolean isGzip()
{
return _resourceService.isGzip();
for (CompressedContentFormat formats : _resourceService.getPrecompressedFormats()) {
if (CompressedContentFormat.GZIP._encoding.equals(formats._encoding)) {
return true;
}
}
return false;
}
/**
* @return Precompressed resources formats that can be used to serve compressed variant of resources.
*/
public CompressedContentFormat[] getPrecompressedFormats()
{
return _resourceService.getPrecompressedFormats();
}
/* ------------------------------------------------------------ */
@ -407,9 +421,10 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory
* @param gzip
* If set to true, then static content will be served as gzip content encoded if a matching resource is found ending with ".gz"
*/
@Deprecated
public void setGzip(boolean gzip)
{
_resourceService.setGzip(gzip);
setPrecompressedFormats(gzip?new CompressedContentFormat[]{CompressedContentFormat.GZIP}:new CompressedContentFormat[0]);
}
/* ------------------------------------------------------------ */
@ -421,6 +436,16 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory
_resourceService.setGzipEquivalentFileExtensions(gzipEquivalentFileExtensions);
}
/**
* @param precompressedFormats
* The list of precompresed formats to serve in encoded format if matching resource found.
* For example serve gzip encoded file if ".gz" suffixed resource is found.
*/
public void setPrecompressedFormats(CompressedContentFormat[] precompressedFormats)
{
_resourceService.setPrecompressedFormats(precompressedFormats);
}
/* ------------------------------------------------------------ */
public void setMimeTypes(MimeTypes mimeTypes)
{

View File

@ -28,7 +28,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.GzipHttpContent;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
@ -102,6 +102,8 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_mimeTypes.exclude("application/zip");
_mimeTypes.exclude("application/gzip");
_mimeTypes.exclude("application/bzip2");
_mimeTypes.exclude("application/brotli");
_mimeTypes.exclude("application/x-xz");
_mimeTypes.exclude("application/x-rar-compressed");
LOG.debug("{} mime types {}",this,_mimeTypes);
@ -473,13 +475,13 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
String etag = baseRequest.getHttpFields().get(HttpHeader.IF_NONE_MATCH);
if (etag!=null)
{
int i=etag.indexOf(GzipHttpContent.ETAG_GZIP_QUOTE);
int i=etag.indexOf(CompressedContentFormat.GZIP._etagQuote);
if (i>0)
{
while (i>=0)
{
etag=etag.substring(0,i)+etag.substring(i+GzipHttpContent.ETAG_GZIP.length());
i=etag.indexOf(GzipHttpContent.ETAG_GZIP_QUOTE,i);
etag=etag.substring(0,i)+etag.substring(i+CompressedContentFormat.GZIP._etag.length());
i=etag.indexOf(CompressedContentFormat.GZIP._etagQuote,i);
}
baseRequest.getHttpFields().put(new HttpField(HttpHeader.IF_NONE_MATCH,etag));
}

View File

@ -24,7 +24,6 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
import org.eclipse.jetty.http.GzipHttpContent;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
@ -40,6 +39,8 @@ import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import static org.eclipse.jetty.http.CompressedContentFormat.GZIP;
public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
{
public static Logger LOG = Log.getLogger(GzipHttpOutputInterceptor.class);
@ -207,7 +208,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
return;
}
fields.put(GzipHttpContent.CONTENT_ENCODING_GZIP);
fields.put(GZIP._contentEncoding);
_crc.reset();
_buffer=_channel.getByteBufferPool().acquire(_bufferSize,false);
BufferUtil.fill(_buffer,GZIP_HEADER,0,GZIP_HEADER.length);
@ -218,7 +219,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
if (etag!=null)
{
int end = etag.length()-1;
etag=(etag.charAt(end)=='"')?etag.substring(0,end)+GzipHttpContent.ETAG_GZIP+'"':etag+GzipHttpContent.ETAG_GZIP;
etag=(etag.charAt(end)=='"')?etag.substring(0,end)+ GZIP._etag+'"':etag+GZIP._etag;
fields.put(HttpHeader.ETAG,etag);
}

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import java.io.BufferedReader;
import java.io.File;
@ -28,6 +27,7 @@ import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.ResourceHttpContent;
@ -51,9 +51,9 @@ public class ResourceCacheTest
Resource[] r = rc.getResources();
MimeTypes mime = new MimeTypes();
ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false,false);
ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false,false);
ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false,false,false);
ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false,CompressedContentFormat.NONE);
ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false,CompressedContentFormat.NONE);
ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false,false,CompressedContentFormat.NONE);
assertEquals("1 - one", getContent(rc1, "1.txt"));
assertEquals("2 - two", getContent(rc1, "2.txt"));
@ -81,8 +81,8 @@ public class ResourceCacheTest
Resource[] r = rc.getResources();
MimeTypes mime = new MimeTypes();
ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false,false);
ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false,false)
ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false,CompressedContentFormat.NONE);
ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false,CompressedContentFormat.NONE)
{
@Override
public boolean isCacheable(Resource resource)
@ -91,7 +91,7 @@ public class ResourceCacheTest
}
};
ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false,false,false);
ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false,false,CompressedContentFormat.NONE);
assertEquals("1 - one", getContent(rc1, "1.txt"));
assertEquals("2 - two", getContent(rc1, "2.txt"));
@ -132,7 +132,7 @@ public class ResourceCacheTest
directory=Resource.newResource(files[0].getParentFile().getAbsolutePath());
cache=new ResourceCache(null,directory,new MimeTypes(),false,false,false);
cache=new ResourceCache(null,directory,new MimeTypes(),false,false,CompressedContentFormat.NONE);
cache.setMaxCacheSize(95);
cache.setMaxCachedFileSize(85);
@ -159,7 +159,7 @@ public class ResourceCacheTest
assertEquals(0,cache.getCachedSize());
assertEquals(0,cache.getCachedFiles());
cache=new ResourceCache(null,directory,new MimeTypes(),true,false,false);
cache=new ResourceCache(null,directory,new MimeTypes(),true,false,CompressedContentFormat.NONE);
cache.setMaxCacheSize(95);
cache.setMaxCachedFileSize(85);
cache.setMaxCachedFiles(4);
@ -284,7 +284,7 @@ public class ResourceCacheTest
Resource[] resources = rc.getResources();
MimeTypes mime = new MimeTypes();
ResourceCache cache = new ResourceCache(null,resources[0],mime,false,false,false);
ResourceCache cache = new ResourceCache(null,resources[0],mime,false,false,CompressedContentFormat.NONE);
assertEquals("4 - four", getContent(cache, "four.txt"));
assertEquals("4 - four (no extension)", getContent(cache, "four"));

View File

@ -31,6 +31,7 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.MimeTypes;
@ -196,7 +197,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
_resourceService.setAcceptRanges(getInitBoolean("acceptRanges",_resourceService.isAcceptRanges()));
_resourceService.setDirAllowed(getInitBoolean("dirAllowed",_resourceService.isDirAllowed()));
_resourceService.setRedirectWelcome(getInitBoolean("redirectWelcome",_resourceService.isRedirectWelcome()));
_resourceService.setGzip(getInitBoolean("gzip",_resourceService.isGzip()));
_resourceService.setPrecompressedFormats(parsePrecompressedFormats(getInitParameter("precompressed"), getInitBoolean("gzip", false)));
_resourceService.setPathInfoOnly(getInitBoolean("pathInfoOnly",_resourceService.isPathInfoOnly()));
_resourceService.setEtags(getInitBoolean("etags",_resourceService.isEtags()));
@ -270,7 +271,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
{
if (_cache==null && (max_cached_files!=-2 || max_cache_size!=-2 || max_cached_file_size!=-2))
{
_cache = new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_resourceService.isEtags(),_resourceService.isGzip());
_cache = new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_resourceService.isEtags(),_resourceService.getPrecompressedFormats());
if (max_cache_size>=0)
_cache.setMaxCacheSize(max_cache_size);
if (max_cached_file_size>=-1)
@ -289,7 +290,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
HttpContent.Factory contentFactory=_cache;
if (contentFactory==null)
{
contentFactory=new ResourceContentFactory(this,_mimeTypes,_resourceService.isGzip());
contentFactory=new ResourceContentFactory(this,_mimeTypes,_resourceService.getPrecompressedFormats());
if (resourceCache!=null)
_servletContext.setAttribute(resourceCache,contentFactory);
}
@ -310,11 +311,11 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
else
{
//.svgz files are gzipped svg files and must be served with Content-Encoding:gzip
gzip_equivalent_file_extensions.add(".svgz");
gzip_equivalent_file_extensions.add(".svgz");
}
_resourceService.setGzipEquivalentFileExtensions(gzip_equivalent_file_extensions);
_servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
for (ServletHolder h :_servletHandler.getServlets())
if (h.getServletInstance()==this)
@ -324,6 +325,27 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
LOG.debug("resource base = "+_resourceBase);
}
private CompressedContentFormat[] parsePrecompressedFormats(String precompressed, boolean gzip) {
List<CompressedContentFormat> ret = new ArrayList<>();
if (precompressed != null && precompressed.indexOf('=') > 0) {
for (String pair : precompressed.split(",")) {
String[] setting = pair.split("=");
String encoding = setting[0];
String extension = setting[1];
ret.add(new CompressedContentFormat(encoding, extension));
}
} else if (precompressed != null) {
if (Boolean.parseBoolean(precompressed)) {
ret.add(CompressedContentFormat.BR);
ret.add(CompressedContentFormat.GZIP);
}
} else if (gzip) {
// gzip handling is for backwards compatibility with older Jetty
ret.add(CompressedContentFormat.GZIP);
}
return ret.toArray(new CompressedContentFormat[ret.size()]);
}
/**
* Compute the field _contextHandler.<br>
* In the case where the DefaultServlet is deployed on the HttpService it is likely that

View File

@ -26,7 +26,6 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.EnumSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -900,6 +899,150 @@ public class DefaultServletTest
assertResponseContains("ETag: "+etag,response);
}
@Test
public void testBrotli() throws Exception
{
testdir.ensureEmpty();
File resBase = testdir.getPathFile("docroot").toFile();
FS.ensureDirExists(resBase);
File file0 = new File(resBase, "data0.txt");
createFile(file0, "Hello Text 0");
File file0br = new File(resBase, "data0.txt.br");
createFile(file0br, "fake brotli");
String resBasePath = resBase.getAbsolutePath();
ServletHolder defholder = context.addServlet(DefaultServlet.class, "/");
defholder.setInitParameter("dirAllowed", "false");
defholder.setInitParameter("redirectWelcome", "false");
defholder.setInitParameter("welcomeServlets", "false");
defholder.setInitParameter("precompressed", "true");
defholder.setInitParameter("etags", "true");
defholder.setInitParameter("resourceBase", resBasePath);
String response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\n\r\n");
assertResponseContains("Content-Length: 12", response);
assertResponseContains("Content-Type: text/plain",response);
assertResponseContains("Hello Text 0",response);
assertResponseContains("Vary: Accept-Encoding",response);
assertResponseContains("ETag: ",response);
assertResponseNotContains("Content-Encoding: br",response);
int e=response.indexOf("ETag: ");
String etag = response.substring(e+6,response.indexOf('"',e+11)+1);
String etag_br = etag.substring(0,etag.length()-1)+"--br\"";
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip;q=0.9,br\r\n\r\n");
assertResponseContains("Content-Length: 11", response);
assertResponseContains("fake br",response);
assertResponseContains("Content-Type: text/plain",response);
assertResponseContains("Vary: Accept-Encoding",response);
assertResponseContains("Content-Encoding: br",response);
assertResponseContains("ETag: "+etag_br,response);
response = connector.getResponses("GET /context/data0.txt.br HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:br,gzip\r\n\r\n");
assertResponseContains("Content-Length: 11", response);
assertResponseContains("fake br",response);
assertResponseContains("Content-Type: application/brotli",response);
assertResponseNotContains("Vary: Accept-Encoding",response);
assertResponseNotContains("Content-Encoding: ",response);
assertResponseNotContains("ETag: "+etag_br,response);
assertResponseContains("ETag: ",response);
response = connector.getResponses("GET /context/data0.txt.br HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\nIf-None-Match: W/\"wobble\"\r\n\r\n");
assertResponseContains("Content-Length: 11", response);
assertResponseContains("fake brotli",response);
assertResponseContains("Content-Type: application/brotli",response);
assertResponseNotContains("Vary: Accept-Encoding",response);
assertResponseNotContains("Content-Encoding: ",response);
assertResponseNotContains("ETag: "+etag_br,response);
assertResponseContains("ETag: ",response);
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:br\r\nIf-None-Match: "+etag_br+"\r\n\r\n");
assertResponseContains("304 Not Modified", response);
assertResponseContains("ETag: "+etag_br,response);
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:br\r\nIf-None-Match: "+etag+"\r\n\r\n");
assertResponseContains("304 Not Modified", response);
assertResponseContains("ETag: "+etag,response);
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:br\r\nIf-None-Match: W/\"foobar\","+etag_br+"\r\n\r\n");
assertResponseContains("304 Not Modified", response);
assertResponseContains("ETag: "+etag_br,response);
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:br\r\nIf-None-Match: W/\"foobar\","+etag+"\r\n\r\n");
assertResponseContains("304 Not Modified", response);
assertResponseContains("ETag: "+etag,response);
}
@Test
public void testCachedBrotli() throws Exception
{
testdir.ensureEmpty();
File resBase = testdir.getPathFile("docroot").toFile();
FS.ensureDirExists(resBase);
File file0 = new File(resBase, "data0.txt");
createFile(file0, "Hello Text 0");
File file0br = new File(resBase, "data0.txt.br");
createFile(file0br, "fake brotli");
String resBasePath = resBase.getAbsolutePath();
ServletHolder defholder = context.addServlet(DefaultServlet.class, "/");
defholder.setInitParameter("dirAllowed", "false");
defholder.setInitParameter("redirectWelcome", "false");
defholder.setInitParameter("welcomeServlets", "false");
defholder.setInitParameter("precompressed", "true");
defholder.setInitParameter("etags", "true");
defholder.setInitParameter("resourceBase", resBasePath);
defholder.setInitParameter("maxCachedFiles", "1024");
defholder.setInitParameter("maxCachedFileSize", "200000000");
defholder.setInitParameter("maxCacheSize", "256000000");
String response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\n\r\n");
assertResponseContains("Content-Length: 12", response);
assertResponseContains("Content-Type: text/plain",response);
assertResponseContains("Hello Text 0",response);
assertResponseContains("Vary: Accept-Encoding",response);
assertResponseContains("ETag: ",response);
assertResponseNotContains("Content-Encoding: ",response);
int e=response.indexOf("ETag: ");
String etag = response.substring(e+6,response.indexOf('"',e+11)+1);
String etag_gzip = etag.substring(0,etag.length()-1)+"--br\"";
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:br\r\n\r\n");
assertResponseContains("Content-Length: 11", response);
assertResponseContains("fake brotli",response);
assertResponseContains("Content-Type: text/plain",response);
assertResponseContains("Vary: Accept-Encoding",response);
assertResponseContains("Content-Encoding: br",response);
assertResponseContains("ETag: "+etag_gzip,response);
response = connector.getResponses("GET /context/data0.txt.br HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:br\r\n\r\n");
assertResponseContains("Content-Length: 11", response);
assertResponseContains("fake brotli",response);
assertResponseContains("Content-Type: application/br",response);
assertResponseNotContains("Vary: Accept-Encoding",response);
assertResponseNotContains("Content-Encoding: ",response);
assertResponseNotContains("ETag: "+etag_gzip,response);
assertResponseContains("ETag: ",response);
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:br\r\nIf-None-Match: "+etag_gzip+"\r\n\r\n");
assertResponseContains("304 Not Modified", response);
assertResponseContains("ETag: "+etag_gzip,response);
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:br\r\nIf-None-Match: "+etag+"\r\n\r\n");
assertResponseContains("304 Not Modified", response);
assertResponseContains("ETag: "+etag,response);
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:br\r\nIf-None-Match: W/\"foobar\","+etag_gzip+"\r\n\r\n");
assertResponseContains("304 Not Modified", response);
assertResponseContains("ETag: "+etag_gzip,response);
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:br\r\nIf-None-Match: W/\"foobar\","+etag+"\r\n\r\n");
assertResponseContains("304 Not Modified", response);
assertResponseContains("ETag: "+etag,response);
}
@Test
public void testIfModifiedSmall() throws Exception
{

View File

@ -47,6 +47,7 @@ public class GzipDefaultNoRecompressTest
{
// Some already compressed files
{ "test_quotes.gz", "application/gzip" , GzipHandler.GZIP },
{ "test_quotes.br", "application/brotli" , GzipHandler.GZIP },
{ "test_quotes.bz2", "application/bzip2", GzipHandler.GZIP },
{ "test_quotes.zip", "application/zip" , GzipHandler.GZIP },
{ "test_quotes.rar", "application/x-rar-compressed", GzipHandler.GZIP },

View File

@ -38,8 +38,7 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.GzipHttpContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.servlet.DefaultServlet;
@ -148,7 +147,7 @@ public class GzipDefaultTest
//A HEAD request should have similar headers, but no body
response = tester.executeRequest("HEAD","/context/file.txt",5,TimeUnit.SECONDS);
assertThat("Response status",response.getStatus(),is(HttpStatus.OK_200));
assertThat("ETag", response.get("ETag"), containsString(GzipHttpContent.ETAG_GZIP));
assertThat("ETag", response.get("ETag"), containsString(CompressedContentFormat.GZIP._etag));
assertThat("Content encoding", response.get("Content-Encoding"), containsString("gzip"));
assertNull("Content length", response.get("Content-Length"));

View File

@ -43,6 +43,7 @@ public class TestStaticMimeTypeServlet extends TestDirContentServlet
mimeTypes = new MimeTypes();
// Some real world, yet not terribly common, mime type mappings.
mimeTypes.addMimeMapping("bz2","application/bzip2");
mimeTypes.addMimeMapping("br","application/brotli");
mimeTypes.addMimeMapping("bmp","image/bmp");
mimeTypes.addMimeMapping("tga","application/tga");
mimeTypes.addMimeMapping("xcf","image/xcf");

Binary file not shown.

View File

@ -0,0 +1 @@
541425b47b6fe9f088383c258b5d337492325b47 test_quotes.br