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:
parent
21e4cfecfc
commit
3261e03edb
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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"));
|
||||
|
||||
|
|
|
@ -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.
|
@ -0,0 +1 @@
|
|||
541425b47b6fe9f088383c258b5d337492325b47 test_quotes.br
|
Loading…
Reference in New Issue