Merge remote-tracking branch 'origin/jetty-7' into jetty-8

Conflicts:
	jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java
This commit is contained in:
Greg Wilkins 2012-11-12 09:08:53 +11:00
commit 3d3b035b22
20 changed files with 780 additions and 162 deletions

View File

@ -38,6 +38,7 @@ public interface HttpContent
Buffer getLastModified(); Buffer getLastModified();
Buffer getIndirectBuffer(); Buffer getIndirectBuffer();
Buffer getDirectBuffer(); Buffer getDirectBuffer();
Buffer getETag();
Resource getResource(); Resource getResource();
long getContentLength(); long getContentLength();
InputStream getInputStream() throws IOException; InputStream getInputStream() throws IOException;
@ -53,19 +54,33 @@ public interface HttpContent
final Resource _resource; final Resource _resource;
final Buffer _mimeType; final Buffer _mimeType;
final int _maxBuffer; final int _maxBuffer;
final Buffer _etag;
/* ------------------------------------------------------------ */
public ResourceAsHttpContent(final Resource resource, final Buffer mimeType) public ResourceAsHttpContent(final Resource resource, final Buffer mimeType)
{ {
_resource=resource; this(resource,mimeType,-1,false);
_mimeType=mimeType;
_maxBuffer=-1;
} }
/* ------------------------------------------------------------ */
public ResourceAsHttpContent(final Resource resource, final Buffer mimeType, int maxBuffer) public ResourceAsHttpContent(final Resource resource, final Buffer mimeType, int maxBuffer)
{
this(resource,mimeType,maxBuffer,false);
}
/* ------------------------------------------------------------ */
public ResourceAsHttpContent(final Resource resource, final Buffer mimeType, boolean etag)
{
this(resource,mimeType,-1,etag);
}
/* ------------------------------------------------------------ */
public ResourceAsHttpContent(final Resource resource, final Buffer mimeType, int maxBuffer, boolean etag)
{ {
_resource=resource; _resource=resource;
_mimeType=mimeType; _mimeType=mimeType;
_maxBuffer=maxBuffer; _maxBuffer=maxBuffer;
_etag=etag?new ByteArrayBuffer(resource.getWeakETag()):null;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -86,6 +101,12 @@ public interface HttpContent
return null; return null;
} }
/* ------------------------------------------------------------ */
public Buffer getETag()
{
return _etag;
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public Buffer getIndirectBuffer() public Buffer getIndirectBuffer()
{ {

View File

@ -40,43 +40,26 @@ import org.eclipse.jetty.util.ByteArrayOutputStream2;
public abstract class AbstractCompressedStream extends ServletOutputStream public abstract class AbstractCompressedStream extends ServletOutputStream
{ {
private final String _encoding; private final String _encoding;
protected HttpServletRequest _request; protected final CompressedResponseWrapper _wrapper;
protected HttpServletResponse _response; protected final HttpServletResponse _response;
protected OutputStream _out; protected OutputStream _out;
protected ByteArrayOutputStream2 _bOut; protected ByteArrayOutputStream2 _bOut;
protected DeflaterOutputStream _compressedOutputStream; protected DeflaterOutputStream _compressedOutputStream;
protected boolean _closed; protected boolean _closed;
protected int _bufferSize;
protected int _minCompressSize;
protected long _contentLength;
protected boolean _doNotCompress; protected boolean _doNotCompress;
/** /**
* Instantiates a new compressed stream. * Instantiates a new compressed stream.
* *
* @param request
* the request
* @param response
* the response
* @param contentLength
* the content length
* @param bufferSize
* the buffer size
* @param minCompressSize
* the min compress size
* @throws IOException
* Signals that an I/O exception has occurred.
*/ */
public AbstractCompressedStream(String encoding,HttpServletRequest request, HttpServletResponse response, long contentLength, int bufferSize, int minCompressSize) public AbstractCompressedStream(String encoding,HttpServletRequest request, CompressedResponseWrapper wrapper)
throws IOException throws IOException
{ {
_encoding=encoding; _encoding=encoding;
_request = request; _wrapper = wrapper;
_response = response; _response = (HttpServletResponse)wrapper.getResponse();
_contentLength = contentLength;
_bufferSize = bufferSize; if (_wrapper.getMinCompressSize()==0)
_minCompressSize = minCompressSize;
if (minCompressSize == 0)
doCompress(); doCompress();
} }
@ -96,21 +79,19 @@ public abstract class AbstractCompressedStream extends ServletOutputStream
_doNotCompress = false; _doNotCompress = false;
} }
/** /* ------------------------------------------------------------ */
* Sets the content length. public void setContentLength()
*
* @param length
* the new content length
*/
public void setContentLength(long length)
{ {
_contentLength = length; if (_doNotCompress)
if (_doNotCompress && length >= 0)
{ {
if (_contentLength < Integer.MAX_VALUE) long length=_wrapper.getContentLength();
_response.setContentLength((int)_contentLength); if (length>=0)
else {
_response.setHeader("Content-Length",Long.toString(_contentLength)); if (length < Integer.MAX_VALUE)
_response.setContentLength((int)length);
else
_response.setHeader("Content-Length",Long.toString(length));
}
} }
} }
@ -123,7 +104,8 @@ public abstract class AbstractCompressedStream extends ServletOutputStream
{ {
if (_out == null || _bOut != null) if (_out == null || _bOut != null)
{ {
if (_contentLength > 0 && _contentLength < _minCompressSize) long length=_wrapper.getContentLength();
if (length > 0 && length < _wrapper.getMinCompressSize())
doNotCompress(); doNotCompress();
else else
doCompress(); doCompress();
@ -142,15 +124,19 @@ public abstract class AbstractCompressedStream extends ServletOutputStream
if (_closed) if (_closed)
return; return;
if (_request.getAttribute("javax.servlet.include.request_uri") != null) if (_wrapper.getRequest().getAttribute("javax.servlet.include.request_uri") != null)
flush(); flush();
else else
{ {
if (_bOut != null) if (_bOut != null)
{ {
if (_contentLength < 0) long length=_wrapper.getContentLength();
_contentLength = _bOut.getCount(); if (length < 0)
if (_contentLength < _minCompressSize) {
length = _bOut.getCount();
_wrapper.setContentLength(length);
}
if (length < _wrapper.getMinCompressSize())
doNotCompress(); doNotCompress();
else else
doCompress(); doCompress();
@ -180,7 +166,8 @@ public abstract class AbstractCompressedStream extends ServletOutputStream
{ {
if (_out == null || _bOut != null) if (_out == null || _bOut != null)
{ {
if (_contentLength > 0 && _contentLength < _minCompressSize) long length=_wrapper.getContentLength();
if (length > 0 && length < _wrapper.getMinCompressSize())
doNotCompress(); doNotCompress();
else else
doCompress(); doCompress();
@ -249,6 +236,10 @@ public abstract class AbstractCompressedStream extends ServletOutputStream
_out.write(_bOut.getBuf(),0,_bOut.getCount()); _out.write(_bOut.getBuf(),0,_bOut.getCount());
_bOut=null; _bOut=null;
} }
String etag=_wrapper.getETag();
if (etag!=null)
setHeader("ETag",etag.substring(0,etag.length()-1)+'-'+_encoding+'"');
} }
else else
doNotCompress(); doNotCompress();
@ -267,10 +258,13 @@ public abstract class AbstractCompressedStream extends ServletOutputStream
throw new IllegalStateException("Compressed output stream is already assigned."); throw new IllegalStateException("Compressed output stream is already assigned.");
if (_out == null || _bOut != null) if (_out == null || _bOut != null)
{ {
if (_wrapper.getETag()!=null)
setHeader("ETag",_wrapper.getETag());
_doNotCompress = true; _doNotCompress = true;
_out = _response.getOutputStream(); _out = _response.getOutputStream();
setContentLength(_contentLength); setContentLength();
if (_bOut != null) if (_bOut != null)
_out.write(_bOut.getBuf(),0,_bOut.getCount()); _out.write(_bOut.getBuf(),0,_bOut.getCount());
@ -281,30 +275,32 @@ public abstract class AbstractCompressedStream extends ServletOutputStream
/** /**
* Check out. * Check out.
* *
* @param length * @param lengthToWrite
* the length * the length
* @throws IOException * @throws IOException
* Signals that an I/O exception has occurred. * Signals that an I/O exception has occurred.
*/ */
private void checkOut(int length) throws IOException private void checkOut(int lengthToWrite) throws IOException
{ {
if (_closed) if (_closed)
throw new IOException("CLOSED"); throw new IOException("CLOSED");
if (_out == null) if (_out == null)
{ {
if (_response.isCommitted() || (_contentLength >= 0 && _contentLength < _minCompressSize)) long length=_wrapper.getContentLength();
if (_response.isCommitted() || (length >= 0 && length < _wrapper.getMinCompressSize()))
doNotCompress(); doNotCompress();
else if (length > _minCompressSize) else if (lengthToWrite > _wrapper.getMinCompressSize())
doCompress(); doCompress();
else else
_out = _bOut = new ByteArrayOutputStream2(_bufferSize); _out = _bOut = new ByteArrayOutputStream2(_wrapper.getBufferSize());
} }
else if (_bOut != null) else if (_bOut != null)
{ {
if (_response.isCommitted() || (_contentLength >= 0 && _contentLength < _minCompressSize)) long length=_wrapper.getContentLength();
if (_response.isCommitted() || (length >= 0 && length < _wrapper.getMinCompressSize()))
doNotCompress(); doNotCompress();
else if (length >= (_bOut.getBuf().length - _bOut.getCount())) else if (lengthToWrite >= (_bOut.getBuf().length - _bOut.getCount()))
doCompress(); doCompress();
} }
} }

View File

@ -48,15 +48,48 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp
private PrintWriter _writer; private PrintWriter _writer;
private AbstractCompressedStream _compressedStream; private AbstractCompressedStream _compressedStream;
private String _etag;
private long _contentLength=-1; private long _contentLength=-1;
private boolean _noCompression; private boolean _noCompression;
/* ------------------------------------------------------------ */
public CompressedResponseWrapper(HttpServletRequest request, HttpServletResponse response) public CompressedResponseWrapper(HttpServletRequest request, HttpServletResponse response)
{ {
super(response); super(response);
_request = request; _request = request;
} }
/* ------------------------------------------------------------ */
public long getContentLength()
{
return _contentLength;
}
/* ------------------------------------------------------------ */
public int getBufferSize()
{
return _bufferSize;
}
/* ------------------------------------------------------------ */
public int getMinCompressSize()
{
return _minCompressSize;
}
/* ------------------------------------------------------------ */
public String getETag()
{
return _etag;
}
/* ------------------------------------------------------------ */
public HttpServletRequest getRequest()
{
return _request;
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setMimeTypes(java.util.Set) * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setMimeTypes(java.util.Set)
@ -148,7 +181,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp
{ {
_contentLength=length; _contentLength=length;
if (_compressedStream!=null) if (_compressedStream!=null)
_compressedStream.setContentLength(length); _compressedStream.setContentLength();
else if (_noCompression && _contentLength>=0) else if (_noCompression && _contentLength>=0)
{ {
HttpServletResponse response = (HttpServletResponse)getResponse(); HttpServletResponse response = (HttpServletResponse)getResponse();
@ -174,7 +207,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp
{ {
_contentLength=Long.parseLong(value); _contentLength=Long.parseLong(value);
if (_compressedStream!=null) if (_compressedStream!=null)
_compressedStream.setContentLength(_contentLength); _compressedStream.setContentLength();
} }
else if ("content-type".equalsIgnoreCase(name)) else if ("content-type".equalsIgnoreCase(name))
{ {
@ -188,6 +221,8 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp
noCompression(); noCompression();
} }
} }
else if ("etag".equalsIgnoreCase(name))
_etag=value;
else else
super.addHeader(name,value); super.addHeader(name,value);
} }
@ -325,10 +360,21 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp
noCompression(); noCompression();
} }
} }
else if ("etag".equalsIgnoreCase(name))
_etag=value;
else else
super.setHeader(name,value); super.setHeader(name,value);
} }
/* ------------------------------------------------------------ */
@Override
public boolean containsHeader(String name)
{
if ("etag".equalsIgnoreCase(name) && _etag!=null)
return true;
return super.containsHeader(name);
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#getOutputStream() * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#getOutputStream()
@ -344,7 +390,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp
return getResponse().getOutputStream(); return getResponse().getOutputStream();
} }
_compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minCompressSize); _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse());
} }
else if (_writer!=null) else if (_writer!=null)
throw new IllegalStateException("getWriter() called"); throw new IllegalStateException("getWriter() called");
@ -370,7 +416,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp
return getResponse().getWriter(); return getResponse().getWriter();
} }
_compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minCompressSize); _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse());
_writer=newWriter(_compressedStream,getCharacterEncoding()); _writer=newWriter(_compressedStream,getCharacterEncoding());
} }
return _writer; return _writer;
@ -387,7 +433,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp
{ {
_contentLength=value; _contentLength=value;
if (_compressedStream!=null) if (_compressedStream!=null)
_compressedStream.setContentLength(_contentLength); _compressedStream.setContentLength();
} }
else else
super.setIntHeader(name,value); super.setIntHeader(name,value);
@ -411,6 +457,6 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp
/** /**
*@return the underlying CompressedStream implementation *@return the underlying CompressedStream implementation
*/ */
protected abstract AbstractCompressedStream newCompressedStream(HttpServletRequest _request, HttpServletResponse response, long _contentLength2, int _bufferSize2, int _minCompressedSize2) throws IOException; protected abstract AbstractCompressedStream newCompressedStream(HttpServletRequest _request, HttpServletResponse response) throws IOException;
} }

View File

@ -432,7 +432,7 @@ public class OverlayedAppProvider extends AbstractLifeCycle implements AppProvid
context.setBaseResource(new ResourceCollection(instance_webapp,shared.getBaseResource())); context.setBaseResource(new ResourceCollection(instance_webapp,shared.getBaseResource()));
// Create the resource cache // Create the resource cache
ResourceCache cache = new ResourceCache(shared.getResourceCache(),instance_webapp,context.getMimeTypes()); ResourceCache cache = new ResourceCache(shared.getResourceCache(),instance_webapp,context.getMimeTypes(),false,false);
context.setAttribute(ResourceCache.class.getCanonicalName(),cache); context.setAttribute(ResourceCache.class.getCanonicalName(),cache);
} }
else else

View File

@ -83,7 +83,7 @@ public class TemplateContext extends AggregateLifeCycle implements WebAppClassLo
_server=server; _server=server;
_baseResource=baseResource; _baseResource=baseResource;
_mimeTypes=new MimeTypes(); _mimeTypes=new MimeTypes();
_resourceCache=new ResourceCache(null,baseResource,_mimeTypes); _resourceCache=new ResourceCache(null,baseResource,_mimeTypes,false,false);
String[] patterns = (String[])_server.getAttribute(WebAppContext.SERVER_SRV_CLASSES); String[] patterns = (String[])_server.getAttribute(WebAppContext.SERVER_SRV_CLASSES);
_serverClasses=new ClasspathPattern(patterns==null?WebAppContext.__dftServerClasses:patterns); _serverClasses=new ClasspathPattern(patterns==null?WebAppContext.__dftServerClasses:patterns);

View File

@ -1111,7 +1111,7 @@ public abstract class AbstractHttpConnection extends AbstractConnection
if (super._generator.isWritten()) if (super._generator.isWritten())
throw new IllegalStateException("!empty"); throw new IllegalStateException("!empty");
// Convert HTTP content to contentl // Convert HTTP content to content
if (content instanceof HttpContent) if (content instanceof HttpContent)
{ {
HttpContent httpContent = (HttpContent) content; HttpContent httpContent = (HttpContent) content;
@ -1146,13 +1146,20 @@ public abstract class AbstractHttpConnection extends AbstractConnection
Buffer lm = httpContent.getLastModified(); Buffer lm = httpContent.getLastModified();
long lml=httpContent.getResource().lastModified(); long lml=httpContent.getResource().lastModified();
if (lm != null) if (lm != null)
{
_responseFields.put(HttpHeaders.LAST_MODIFIED_BUFFER, lm); _responseFields.put(HttpHeaders.LAST_MODIFIED_BUFFER, lm);
}
else if (httpContent.getResource()!=null) else if (httpContent.getResource()!=null)
{ {
if (lml!=-1) if (lml!=-1)
_responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, lml); _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, lml);
} }
Buffer etag=httpContent.getETag();
if (etag!=null)
_responseFields.put(HttpHeaders.ETAG_BUFFER,etag);
boolean direct=_connector instanceof NIOConnector && ((NIOConnector)_connector).getUseDirectBuffers() && !(_connector instanceof SslConnector); boolean direct=_connector instanceof NIOConnector && ((NIOConnector)_connector).getUseDirectBuffers() && !(_connector instanceof SslConnector);
content = direct?httpContent.getDirectBuffer():httpContent.getIndirectBuffer(); content = direct?httpContent.getDirectBuffer():httpContent.getIndirectBuffer();
if (content==null) if (content==null)
@ -1200,7 +1207,6 @@ public abstract class AbstractHttpConnection extends AbstractConnection
resource.release(); resource.release();
else else
in.close(); in.close();
} }
} }
else else

View File

@ -58,24 +58,18 @@ public class ResourceCache
private final ResourceFactory _factory; private final ResourceFactory _factory;
private final ResourceCache _parent; private final ResourceCache _parent;
private final MimeTypes _mimeTypes; private final MimeTypes _mimeTypes;
private final boolean _etags;
private boolean _useFileMappedBuffer=true; private boolean _useFileMappedBuffer=true;
private int _maxCachedFileSize =4*1024*1024; private int _maxCachedFileSize =4*1024*1024;
private int _maxCachedFiles=2048; private int _maxCachedFiles=2048;
private int _maxCacheSize =32*1024*1024; private int _maxCacheSize =32*1024*1024;
/* ------------------------------------------------------------ */
public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer)
{
this(parent,factory,mimeTypes);
setUseFileMappedBuffer(useFileMappedBuffer);
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Constructor. /** Constructor.
* @param mimeTypes Mimetype to use for meta data * @param mimeTypes Mimetype to use for meta data
*/ */
public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes) public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags)
{ {
_factory = factory; _factory = factory;
_cache=new ConcurrentHashMap<String,Content>(); _cache=new ConcurrentHashMap<String,Content>();
@ -83,6 +77,7 @@ public class ResourceCache
_cachedFiles=new AtomicInteger(); _cachedFiles=new AtomicInteger();
_mimeTypes=mimeTypes; _mimeTypes=mimeTypes;
_parent=parent; _parent=parent;
_etags=etags;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -249,7 +244,7 @@ public class ResourceCache
return content; return content;
} }
return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize()); return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etags);
} }
@ -361,6 +356,7 @@ public class ResourceCache
final long _lastModified; final long _lastModified;
final Buffer _lastModifiedBytes; final Buffer _lastModifiedBytes;
final Buffer _contentType; final Buffer _contentType;
final Buffer _etagBuffer;
volatile long _lastAccessed; volatile long _lastAccessed;
AtomicReference<Buffer> _indirectBuffer=new AtomicReference<Buffer>(); AtomicReference<Buffer> _indirectBuffer=new AtomicReference<Buffer>();
@ -381,6 +377,8 @@ public class ResourceCache
_cachedSize.addAndGet(_length); _cachedSize.addAndGet(_length);
_cachedFiles.incrementAndGet(); _cachedFiles.incrementAndGet();
_lastAccessed=System.currentTimeMillis(); _lastAccessed=System.currentTimeMillis();
_etagBuffer=_etags?new ByteArrayBuffer(resource.getWeakETag()):null;
} }
@ -408,10 +406,16 @@ public class ResourceCache
return _resource; return _resource;
} }
/* ------------------------------------------------------------ */
public Buffer getETag()
{
return _etagBuffer;
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
boolean isValid() boolean isValid()
{ {
if (_lastModified==_resource.lastModified()) if (_lastModified==_resource.lastModified() && _length==_resource.length())
{ {
_lastAccessed=System.currentTimeMillis(); _lastAccessed=System.currentTimeMillis();
return true; return true;

View File

@ -295,9 +295,9 @@ public class GzipHandler extends HandlerWrapper
} }
@Override @Override
protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minCompressSize) throws IOException protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
{ {
return new AbstractCompressedStream("gzip",request,response,contentLength,bufferSize,minCompressSize) return new AbstractCompressedStream("gzip",request,this)
{ {
@Override @Override
protected DeflaterOutputStream createStream() throws IOException protected DeflaterOutputStream createStream() throws IOException

View File

@ -69,6 +69,7 @@ public class ResourceHandler extends HandlerWrapper
ByteArrayBuffer _cacheControl; ByteArrayBuffer _cacheControl;
boolean _aliases; boolean _aliases;
boolean _directory; boolean _directory;
boolean _etags;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public ResourceHandler() public ResourceHandler()
@ -128,6 +129,24 @@ public class ResourceHandler extends HandlerWrapper
_directory = directory; _directory = directory;
} }
/* ------------------------------------------------------------ */
/**
* @return True if ETag processing is done
*/
public boolean isEtags()
{
return _etags;
}
/* ------------------------------------------------------------ */
/**
* @param etags True if ETag processing is done
*/
public void setEtags(boolean etags)
{
_etags = etags;
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@Override @Override
public void doStart() public void doStart()
@ -419,6 +438,21 @@ public class ResourceHandler extends HandlerWrapper
// set some headers // set some headers
long last_modified=resource.lastModified(); long last_modified=resource.lastModified();
String etag=null;
if (_etags)
{
// simple handling of only a single etag
String ifnm = request.getHeader(HttpHeaders.IF_NONE_MATCH);
etag=resource.getWeakETag();
if (ifnm!=null && resource!=null && ifnm.equals(etag))
{
response.setStatus(HttpStatus.NOT_MODIFIED_304);
baseRequest.getResponse().getHttpFields().put(HttpHeaders.ETAG_BUFFER,etag);
return;
}
}
if (last_modified>0) if (last_modified>0)
{ {
long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE); long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
@ -436,6 +470,9 @@ public class ResourceHandler extends HandlerWrapper
// set the headers // set the headers
doResponseHeaders(response,resource,mime!=null?mime.toString():null); doResponseHeaders(response,resource,mime!=null?mime.toString():null);
response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified); response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified);
if (_etags)
baseRequest.getResponse().getHttpFields().put(HttpHeaders.ETAG_BUFFER,etag);
if(skipContentBody) if(skipContentBody)
return; return;
// Send the content // Send the content

View File

@ -46,9 +46,9 @@ public class ResourceCacheTest
Resource[] r = rc.getResources(); Resource[] r = rc.getResources();
MimeTypes mime = new MimeTypes(); MimeTypes mime = new MimeTypes();
ResourceCache rc3 = new ResourceCache(null,r[2],mime,false); ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false);
ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false); ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false);
ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false); ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false,false);
assertEquals("1 - one", getContent(rc1, "1.txt")); assertEquals("1 - one", getContent(rc1, "1.txt"));
assertEquals("2 - two", getContent(rc1, "2.txt")); assertEquals("2 - two", getContent(rc1, "2.txt"));
@ -76,8 +76,8 @@ public class ResourceCacheTest
Resource[] r = rc.getResources(); Resource[] r = rc.getResources();
MimeTypes mime = new MimeTypes(); MimeTypes mime = new MimeTypes();
ResourceCache rc3 = new ResourceCache(null,r[2],mime,false); ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false);
ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false) ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false)
{ {
@Override @Override
public boolean isCacheable(Resource resource) public boolean isCacheable(Resource resource)
@ -86,7 +86,7 @@ public class ResourceCacheTest
} }
}; };
ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false); ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false,false);
assertEquals("1 - one", getContent(rc1, "1.txt")); assertEquals("1 - one", getContent(rc1, "1.txt"));
assertEquals("2 - two", getContent(rc1, "2.txt")); assertEquals("2 - two", getContent(rc1, "2.txt"));
@ -126,7 +126,7 @@ public class ResourceCacheTest
directory=Resource.newResource(files[0].getParentFile().getAbsolutePath()); directory=Resource.newResource(files[0].getParentFile().getAbsolutePath());
cache=new ResourceCache(null,directory,new MimeTypes(),false); cache=new ResourceCache(null,directory,new MimeTypes(),false,false);
cache.setMaxCacheSize(95); cache.setMaxCacheSize(95);
cache.setMaxCachedFileSize(85); cache.setMaxCachedFileSize(85);

View File

@ -58,6 +58,7 @@ import org.eclipse.jetty.server.nio.NIOConnector;
import org.eclipse.jetty.server.ssl.SslConnector; import org.eclipse.jetty.server.ssl.SslConnector;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiPartOutputStream; import org.eclipse.jetty.util.MultiPartOutputStream;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -117,6 +118,8 @@ import org.eclipse.jetty.util.resource.ResourceFactory;
* aliases If True, aliases of resources are allowed (eg. symbolic * aliases If True, aliases of resources are allowed (eg. symbolic
* links and caps variations). May bypass security constraints. * links and caps variations). May bypass security constraints.
* *
* etags If True, weak etags will be handled.
*
* maxCacheSize The maximum total size of the cache or 0 for no cache. * maxCacheSize The maximum total size of the cache or 0 for no cache.
* maxCachedFileSize The maximum size of a file to cache * maxCachedFileSize The maximum size of a file to cache
* maxCachedFiles The maximum number of files to cache * maxCachedFiles The maximum number of files to cache
@ -152,6 +155,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
private boolean _redirectWelcome=false; private boolean _redirectWelcome=false;
private boolean _gzip=true; private boolean _gzip=true;
private boolean _pathInfoOnly=false; private boolean _pathInfoOnly=false;
private boolean _etags=false;
private Resource _resourceBase; private Resource _resourceBase;
private ResourceCache _cache; private ResourceCache _cache;
@ -262,11 +266,13 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
LOG.debug("Cache {}={}",resourceCache,_cache); LOG.debug("Cache {}={}",resourceCache,_cache);
} }
_etags = getInitBoolean("etags",_etags);
try try
{ {
if (_cache==null && max_cached_files>0) if (_cache==null && max_cached_files>0)
{ {
_cache= new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer); _cache= new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags);
if (max_cache_size>0) if (max_cache_size>0)
_cache.setMaxCacheSize(max_cache_size); _cache.setMaxCacheSize(max_cache_size);
@ -287,6 +293,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
if (h.getServletInstance()==this) if (h.getServletInstance()==this)
_defaultHolder=h; _defaultHolder=h;
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("resource base = "+_resourceBase); LOG.debug("resource base = "+_resourceBase);
} }
@ -499,7 +506,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
{ {
// ensure we have content // ensure we have content
if (content==null) if (content==null)
content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),response.getBufferSize()); content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),response.getBufferSize(),_etags);
if (included.booleanValue() || passConditionalHeaders(request,response, resource,content)) if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
{ {
@ -570,7 +577,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
} }
else else
{ {
content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString())); content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),_etags);
if (included.booleanValue() || passConditionalHeaders(request,response, resource,content)) if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
sendDirectory(request,response,resource,pathInContext); sendDirectory(request,response,resource,pathInContext);
} }
@ -672,6 +679,75 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
{ {
if (!request.getMethod().equals(HttpMethods.HEAD) ) if (!request.getMethod().equals(HttpMethods.HEAD) )
{ {
if (_etags)
{
String ifm=request.getHeader(HttpHeaders.IF_MATCH);
if (ifm!=null)
{
boolean match=false;
if (content!=null && content.getETag()!=null)
{
QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
while (!match && quoted.hasMoreTokens())
{
String tag = quoted.nextToken();
if (content.getETag().toString().equals(tag))
match=true;
}
}
if (!match)
{
Response r = Response.getResponse(response);
r.reset(true);
r.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
return false;
}
}
String ifnm=request.getHeader(HttpHeaders.IF_NONE_MATCH);
if (ifnm!=null && content!=null && content.getETag()!=null)
{
// Look for GzipFiltered version of etag
if (content.getETag().toString().equals(request.getAttribute("o.e.j.s.GzipFilter.ETag")))
{
Response r = Response.getResponse(response);
r.reset(true);
r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
r.getHttpFields().put(HttpHeaders.ETAG_BUFFER,ifnm);
return false;
}
// Handle special case of exact match.
if (content.getETag().toString().equals(ifnm))
{
Response r = Response.getResponse(response);
r.reset(true);
r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
r.getHttpFields().put(HttpHeaders.ETAG_BUFFER,content.getETag());
return false;
}
// Handle list of tags
QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true);
while (quoted.hasMoreTokens())
{
String tag = quoted.nextToken();
if (content.getETag().toString().equals(tag))
{
Response r = Response.getResponse(response);
r.reset(true);
r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
r.getHttpFields().put(HttpHeaders.ETAG_BUFFER,content.getETag());
return false;
}
}
return true;
}
}
String ifms=request.getHeader(HttpHeaders.IF_MODIFIED_SINCE); String ifms=request.getHeader(HttpHeaders.IF_MODIFIED_SINCE);
if (ifms!=null) if (ifms!=null)
{ {
@ -991,6 +1067,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
r.setLongContentLength(count); r.setLongContentLength(count);
writeOptionHeaders(fields); writeOptionHeaders(fields);
if (_etags)
fields.put(HttpHeaders.ETAG_BUFFER,content.getETag());
} }
else else
{ {
@ -1007,6 +1086,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
} }
writeOptionHeaders(response); writeOptionHeaders(response);
if (_etags)
response.setHeader(HttpHeaders.ETAG,content.getETag().toString());
} }
} }

View File

@ -25,6 +25,8 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.EnumSet; import java.util.EnumSet;
import javax.servlet.DispatcherType; import javax.servlet.DispatcherType;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.Filter; import javax.servlet.Filter;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.FilterConfig; import javax.servlet.FilterConfig;
@ -34,14 +36,17 @@ import javax.servlet.ServletResponse;
import junit.framework.AssertionFailedError; import junit.framework.AssertionFailedError;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.TestingDir; import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.DateCache;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.hamcrest.Matchers;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -579,6 +584,123 @@ public class DefaultServletTest
assertResponseNotContains("Content-Length: 12", response); assertResponseNotContains("Content-Length: 12", response);
} }
@Test
public void testIfModifiedSmall() throws Exception
{
testIfModified("Hello World");
}
@Test
public void testIfModifiedLarge() throws Exception
{
testIfModified("Now is the time for all good men to come to the aid of the party");
}
public void testIfModified(String content) throws Exception
{
testdir.ensureEmpty();
File resBase = testdir.getFile("docroot");
FS.ensureDirExists(resBase);
File file = new File(resBase, "file.txt");
String resBasePath = resBase.getAbsolutePath();
ServletHolder defholder = context.addServlet(DefaultServlet.class, "/");
defholder.setInitParameter("resourceBase", resBasePath);
defholder.setInitParameter("maxCacheSize", "4096");
defholder.setInitParameter("maxCachedFileSize", "25");
defholder.setInitParameter("maxCachedFiles", "100");
String response = connector.getResponses("GET /context/file.txt HTTP/1.0\r\n\r\n");
assertResponseContains("404", response);
createFile(file, content);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\n\r\n");
assertResponseContains("200", response);
assertResponseContains("Last-Modified", response);
String last_modified = getHeaderValue("Last-Modified",response);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+last_modified+"\r\n\r\n");
assertResponseContains("304", response);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+HttpFields.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n");
assertResponseContains("200", response);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+HttpFields.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n");
assertResponseContains("304", response);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+HttpFields.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n");
assertResponseContains("200", response);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+HttpFields.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n");
assertResponseContains("412", response);
}
@Test
public void testIfETagSmall() throws Exception
{
testIfETag("Hello World");
}
@Test
public void testIfETagLarge() throws Exception
{
testIfETag("Now is the time for all good men to come to the aid of the party");
}
public void testIfETag(String content) throws Exception
{
testdir.ensureEmpty();
File resBase = testdir.getFile("docroot");
FS.ensureDirExists(resBase);
File file = new File(resBase, "file.txt");
String resBasePath = resBase.getAbsolutePath();
ServletHolder defholder = context.addServlet(DefaultServlet.class, "/");
defholder.setInitParameter("resourceBase", resBasePath);
defholder.setInitParameter("maxCacheSize", "4096");
defholder.setInitParameter("maxCachedFileSize", "25");
defholder.setInitParameter("maxCachedFiles", "100");
defholder.setInitParameter("etags", "true");
String response;
createFile(file, content);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\n\r\n");
assertResponseContains("200", response);
assertResponseContains("ETag", response);
String etag = getHeaderValue("ETag",response);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-None-Match: "+etag+"\r\n\r\n");
assertResponseContains("304", response);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-None-Match: wibble,"+etag+",wobble\r\n\r\n");
assertResponseContains("304", response);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-None-Match: wibble\r\n\r\n");
assertResponseContains("200", response);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-None-Match: wibble, wobble\r\n\r\n");
assertResponseContains("200", response);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Match: "+etag+"\r\n\r\n");
assertResponseContains("200", response);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Match: wibble,"+etag+",wobble\r\n\r\n");
assertResponseContains("200", response);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Match: wibble\r\n\r\n");
assertResponseContains("412", response);
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Match: wibble, wobble\r\n\r\n");
assertResponseContains("412", response);
}
public static class OutputFilter implements Filter public static class OutputFilter implements Filter
{ {
public void init(FilterConfig filterConfig) throws ServletException public void init(FilterConfig filterConfig) throws ServletException
@ -645,18 +767,8 @@ public class DefaultServletTest
private int assertResponseContains(String expected, String response) private int assertResponseContains(String expected, String response)
{ {
int idx = response.indexOf(expected); Assert.assertThat(response,Matchers.containsString(expected));
if (idx == (-1)) return response.indexOf(expected);
{
// Not found
StringBuffer err = new StringBuffer();
err.append("Response does not contain expected string \"").append(expected).append("\"");
err.append("\n").append(response);
System.err.println(err);
throw new AssertionFailedError(err.toString());
}
return idx;
} }
private void deleteFile(File file) throws IOException private void deleteFile(File file) throws IOException
@ -681,4 +793,13 @@ public class DefaultServletTest
Assert.assertTrue("Deleting: " + file.getName(), file.delete()); Assert.assertTrue("Deleting: " + file.getName(), file.delete());
} }
} }
private String getHeaderValue(String header, String response)
{
Pattern pattern=Pattern.compile("[\\r\\n]"+header+"\\s*:\\s*(.*?)\\s*[\\r\\n]");
Matcher matcher = pattern.matcher(response);
if (matcher.find())
return matcher.group(1);
return null;
}
} }

View File

@ -106,8 +106,10 @@ public class GzipFilter extends UserAgentFilter
{ {
private static final Logger LOG = Log.getLogger(GzipFilter.class); private static final Logger LOG = Log.getLogger(GzipFilter.class);
public final static String GZIP="gzip"; public final static String GZIP="gzip";
public final static String ETAG_GZIP="-gzip\"";
public final static String DEFLATE="deflate"; public final static String DEFLATE="deflate";
public final static String ETAG_DEFLATE="-deflate\"";
public final static String ETAG="o.e.j.s.GzipFilter.ETag";
protected Set<String> _mimeTypes; protected Set<String> _mimeTypes;
protected int _bufferSize=8192; protected int _bufferSize=8192;
@ -235,6 +237,16 @@ public class GzipFilter extends UserAgentFilter
return; return;
} }
// Special handling for etags
String etag = request.getHeader("If-None-Match");
if (etag!=null)
{
if (etag.endsWith(ETAG_GZIP))
request.setAttribute(ETAG,etag.substring(0,etag.length()-ETAG_GZIP.length())+'"');
else if (etag.endsWith(ETAG_DEFLATE))
request.setAttribute(ETAG,etag.substring(0,etag.length()-ETAG_DEFLATE.length())+'"');
}
CompressedResponseWrapper wrappedResponse = createWrappedResponse(request,response,compressionType); CompressedResponseWrapper wrappedResponse = createWrappedResponse(request,response,compressionType);
boolean exceptional=true; boolean exceptional=true;
@ -362,9 +374,9 @@ public class GzipFilter extends UserAgentFilter
wrappedResponse = new CompressedResponseWrapper(request,response) wrappedResponse = new CompressedResponseWrapper(request,response)
{ {
@Override @Override
protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minCompressSize) throws IOException protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
{ {
return new AbstractCompressedStream(compressionType,request,response,contentLength,bufferSize,minCompressSize) return new AbstractCompressedStream(compressionType,request,this)
{ {
@Override @Override
protected DeflaterOutputStream createStream() throws IOException protected DeflaterOutputStream createStream() throws IOException
@ -380,9 +392,9 @@ public class GzipFilter extends UserAgentFilter
wrappedResponse = new CompressedResponseWrapper(request,response) wrappedResponse = new CompressedResponseWrapper(request,response)
{ {
@Override @Override
protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minCompressSize) throws IOException protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
{ {
return new AbstractCompressedStream(compressionType,request,response,contentLength,bufferSize,minCompressSize) return new AbstractCompressedStream(compressionType,request,this)
{ {
@Override @Override
protected DeflaterOutputStream createStream() throws IOException protected DeflaterOutputStream createStream() throws IOException

View File

@ -75,9 +75,9 @@ public class IncludableGzipFilter extends GzipFilter
wrappedResponse = new IncludableResponseWrapper(request,response) wrappedResponse = new IncludableResponseWrapper(request,response)
{ {
@Override @Override
protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minCompressSize) throws IOException protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
{ {
return new AbstractCompressedStream(compressionType,request,response,contentLength,bufferSize,minCompressSize) return new AbstractCompressedStream(compressionType,request,this)
{ {
@Override @Override
protected DeflaterOutputStream createStream() throws IOException protected DeflaterOutputStream createStream() throws IOException
@ -102,9 +102,9 @@ public class IncludableGzipFilter extends GzipFilter
wrappedResponse = new IncludableResponseWrapper(request,response) wrappedResponse = new IncludableResponseWrapper(request,response)
{ {
@Override @Override
protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minCompressSize) throws IOException protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
{ {
return new AbstractCompressedStream(compressionType,request,response,contentLength,bufferSize,minCompressSize) return new AbstractCompressedStream(compressionType,request,this)
{ {
@Override @Override
protected DeflaterOutputStream createStream() throws IOException protected DeflaterOutputStream createStream() throws IOException

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.util; package org.eclipse.jetty.util;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@ -33,8 +34,8 @@ import java.io.UnsupportedEncodingException;
public class B64Code public class B64Code
{ {
// ------------------------------------------------------------------ // ------------------------------------------------------------------
static final char pad='='; static final char __pad='=';
static final char[] rfc1421alphabet= static final char[] __rfc1421alphabet=
{ {
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
@ -42,16 +43,16 @@ public class B64Code
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
}; };
static final byte[] rfc1421nibbles; static final byte[] __rfc1421nibbles;
static static
{ {
rfc1421nibbles=new byte[256]; __rfc1421nibbles=new byte[256];
for (int i=0;i<256;i++) for (int i=0;i<256;i++)
rfc1421nibbles[i]=-1; __rfc1421nibbles[i]=-1;
for (byte b=0;b<64;b++) for (byte b=0;b<64;b++)
rfc1421nibbles[(byte)rfc1421alphabet[b]]=b; __rfc1421nibbles[(byte)__rfc1421alphabet[b]]=b;
rfc1421nibbles[(byte)pad]=0; __rfc1421nibbles[(byte)__pad]=0;
} }
// ------------------------------------------------------------------ // ------------------------------------------------------------------
@ -104,7 +105,54 @@ public class B64Code
*/ */
static public char[] encode(byte[] b) static public char[] encode(byte[] b)
{ {
return encode(b,false); if (b==null)
return null;
int bLen=b.length;
int cLen=((bLen+2)/3)*4;
char c[]=new char[cLen];
int ci=0;
int bi=0;
byte b0, b1, b2;
int stop=(bLen/3)*3;
while (bi<stop)
{
b0=b[bi++];
b1=b[bi++];
b2=b[bi++];
c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
c[ci++]=__rfc1421alphabet[b2&077];
}
if (bLen!=bi)
{
switch (bLen%3)
{
case 2:
b0=b[bi++];
b1=b[bi++];
c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f];
c[ci++]=__pad;
break;
case 1:
b0=b[bi++];
c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f];
c[ci++]=__pad;
c[ci++]=__pad;
break;
default:
break;
}
}
return c;
} }
// ------------------------------------------------------------------ // ------------------------------------------------------------------
@ -120,11 +168,12 @@ public class B64Code
{ {
if (b==null) if (b==null)
return null; return null;
if (!rfc2045)
return encode(b);
int bLen=b.length; int bLen=b.length;
int cLen=((bLen+2)/3)*4; int cLen=((bLen+2)/3)*4;
if (rfc2045) cLen+=2+2*(cLen/76);
cLen+=2+2*cLen/76;
char c[]=new char[cLen]; char c[]=new char[cLen];
int ci=0; int ci=0;
int bi=0; int bi=0;
@ -136,12 +185,12 @@ public class B64Code
b0=b[bi++]; b0=b[bi++];
b1=b[bi++]; b1=b[bi++];
b2=b[bi++]; b2=b[bi++];
c[ci++]=rfc1421alphabet[(b0>>>2)&0x3f]; c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
c[ci++]=rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f]; c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
c[ci++]=rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03]; c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
c[ci++]=rfc1421alphabet[b2&077]; c[ci++]=__rfc1421alphabet[b2&077];
l+=4; l+=4;
if (rfc2045 && l%76==0) if (l%76==0)
{ {
c[ci++]=13; c[ci++]=13;
c[ci++]=10; c[ci++]=10;
@ -155,18 +204,18 @@ public class B64Code
case 2: case 2:
b0=b[bi++]; b0=b[bi++];
b1=b[bi++]; b1=b[bi++];
c[ci++]=rfc1421alphabet[(b0>>>2)&0x3f]; c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
c[ci++]=rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f]; c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
c[ci++]=rfc1421alphabet[(b1<<2)&0x3f]; c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f];
c[ci++]=pad; c[ci++]=__pad;
break; break;
case 1: case 1:
b0=b[bi++]; b0=b[bi++];
c[ci++]=rfc1421alphabet[(b0>>>2)&0x3f]; c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
c[ci++]=rfc1421alphabet[(b0<<4)&0x3f]; c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f];
c[ci++]=pad; c[ci++]=__pad;
c[ci++]=pad; c[ci++]=__pad;
break; break;
default: default:
@ -174,11 +223,8 @@ public class B64Code
} }
} }
if (rfc2045) c[ci++]=13;
{ c[ci++]=10;
c[ci++]=13;
c[ci++]=10;
}
return c; return c;
} }
@ -226,7 +272,7 @@ public class B64Code
throw new IllegalArgumentException("Input block size is not 4"); throw new IllegalArgumentException("Input block size is not 4");
int li=bLen-1; int li=bLen-1;
while (li>=0 && b[li]==(byte)pad) while (li>=0 && b[li]==(byte)__pad)
li--; li--;
if (li<0) if (li<0)
@ -243,10 +289,10 @@ public class B64Code
{ {
while (ri<stop) while (ri<stop)
{ {
b0=rfc1421nibbles[b[bi++]]; b0=__rfc1421nibbles[b[bi++]];
b1=rfc1421nibbles[b[bi++]]; b1=__rfc1421nibbles[b[bi++]];
b2=rfc1421nibbles[b[bi++]]; b2=__rfc1421nibbles[b[bi++]];
b3=rfc1421nibbles[b[bi++]]; b3=__rfc1421nibbles[b[bi++]];
if (b0<0 || b1<0 || b2<0 || b3<0) if (b0<0 || b1<0 || b2<0 || b3<0)
throw new IllegalArgumentException("Not B64 encoded"); throw new IllegalArgumentException("Not B64 encoded");
@ -260,9 +306,9 @@ public class B64Code
switch (rLen%3) switch (rLen%3)
{ {
case 2: case 2:
b0=rfc1421nibbles[b[bi++]]; b0=__rfc1421nibbles[b[bi++]];
b1=rfc1421nibbles[b[bi++]]; b1=__rfc1421nibbles[b[bi++]];
b2=rfc1421nibbles[b[bi++]]; b2=__rfc1421nibbles[b[bi++]];
if (b0<0 || b1<0 || b2<0) if (b0<0 || b1<0 || b2<0)
throw new IllegalArgumentException("Not B64 encoded"); throw new IllegalArgumentException("Not B64 encoded");
r[ri++]=(byte)(b0<<2|b1>>>4); r[ri++]=(byte)(b0<<2|b1>>>4);
@ -270,8 +316,8 @@ public class B64Code
break; break;
case 1: case 1:
b0=rfc1421nibbles[b[bi++]]; b0=__rfc1421nibbles[b[bi++]];
b1=rfc1421nibbles[b[bi++]]; b1=__rfc1421nibbles[b[bi++]];
if (b0<0 || b1<0) if (b0<0 || b1<0)
throw new IllegalArgumentException("Not B64 encoded"); throw new IllegalArgumentException("Not B64 encoded");
r[ri++]=(byte)(b0<<2|b1>>>4); r[ri++]=(byte)(b0<<2|b1>>>4);
@ -314,17 +360,17 @@ public class B64Code
{ {
char c=encoded.charAt(ci++); char c=encoded.charAt(ci++);
if (c==pad) if (c==__pad)
break; break;
if (Character.isWhitespace(c)) if (Character.isWhitespace(c))
continue; continue;
byte nibble=rfc1421nibbles[c]; byte nibble=__rfc1421nibbles[c];
if (nibble<0) if (nibble<0)
throw new IllegalArgumentException("Not B64 encoded"); throw new IllegalArgumentException("Not B64 encoded");
nibbles[s++]=rfc1421nibbles[c]; nibbles[s++]=__rfc1421nibbles[c];
switch(s) switch(s)
{ {
@ -346,4 +392,36 @@ public class B64Code
return bout.toByteArray(); return bout.toByteArray();
} }
/* ------------------------------------------------------------ */
public static void encode(int value,Appendable buf) throws IOException
{
buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]);
buf.append(__rfc1421alphabet[0x3f&((0x03F00000&value)>>20)]);
buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]);
buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]);
buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]);
buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4)]);
buf.append('=');
}
/* ------------------------------------------------------------ */
public static void encode(long lvalue,Appendable buf) throws IOException
{
int value=(int)(0xFFFFFFFC&(lvalue>>32));
buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]);
buf.append(__rfc1421alphabet[0x3f&((0x03F00000&value)>>20)]);
buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]);
buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]);
buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]);
buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4) + (0xf&(int)(lvalue>>28))]);
value=0x0FFFFFFF&(int)lvalue;
buf.append(__rfc1421alphabet[0x3f&((0x0FC00000&value)>>22)]);
buf.append(__rfc1421alphabet[0x3f&((0x003F0000&value)>>16)]);
buf.append(__rfc1421alphabet[0x3f&((0x0000FC00&value)>>10)]);
buf.append(__rfc1421alphabet[0x3f&((0x000003F0&value)>>4)]);
buf.append(__rfc1421alphabet[0x3f&((0x0000000F&value)<<2)]);
}
} }

View File

@ -374,15 +374,10 @@ public class TypeUtil
{ {
try try
{ {
int bi=0xff&b; int d=0xf&((0xF0&b)>>4);
int c='0'+(bi/16)%16; buf.append((char)((d>9?('A'-10):'0')+d));
if (c>'9') d=0xf&b;
c= 'A'+(c-'0'-10); buf.append((char)((d>9?('A'-10):'0')+d));
buf.append((char)c);
c='0'+bi%16;
if (c>'9')
c= 'A'+(c-'0'-10);
buf.append((char)c);
} }
catch(IOException e) catch(IOException e)
{ {
@ -390,6 +385,37 @@ public class TypeUtil
} }
} }
/* ------------------------------------------------------------ */
public static void toHex(int value,Appendable buf) throws IOException
{
int d=0xf&((0xF0000000&value)>>28);
buf.append((char)((d>9?('A'-10):'0')+d));
d=0xf&((0x0F000000&value)>>24);
buf.append((char)((d>9?('A'-10):'0')+d));
d=0xf&((0x00F00000&value)>>20);
buf.append((char)((d>9?('A'-10):'0')+d));
d=0xf&((0x000F0000&value)>>16);
buf.append((char)((d>9?('A'-10):'0')+d));
d=0xf&((0x0000F000&value)>>12);
buf.append((char)((d>9?('A'-10):'0')+d));
d=0xf&((0x00000F00&value)>>8);
buf.append((char)((d>9?('A'-10):'0')+d));
d=0xf&((0x000000F0&value)>>4);
buf.append((char)((d>9?('A'-10):'0')+d));
d=0xf&value;
buf.append((char)((d>9?('A'-10):'0')+d));
Integer.toString(0,36);
}
/* ------------------------------------------------------------ */
public static void toHex(long value,Appendable buf) throws IOException
{
toHex((int)(value>>32),buf);
toHex((int)value,buf);
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public static String toHexString(byte b) public static String toHexString(byte b)
{ {

View File

@ -31,6 +31,7 @@ import java.text.DateFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
@ -49,6 +50,7 @@ public abstract class Resource implements ResourceFactory
public static boolean __defaultUseCaches = true; public static boolean __defaultUseCaches = true;
volatile Object _associate; volatile Object _associate;
/* ------------------------------------------------------------ */
/** /**
* Change the default setting for url connection caches. * Change the default setting for url connection caches.
* Subsequent URLConnections will use this default. * Subsequent URLConnections will use this default.
@ -662,6 +664,31 @@ public abstract class Resource implements ResourceFactory
writeTo(new FileOutputStream(destination),0,-1); writeTo(new FileOutputStream(destination),0,-1);
} }
/* ------------------------------------------------------------ */
public String getWeakETag()
{
try
{
StringBuilder b = new StringBuilder(32);
b.append("W/\"");
String name=getName();
int length=name.length();
long lhash=0;
for (int i=0; i<length;i++)
lhash=31*lhash+name.charAt(i);
B64Code.encode(lastModified()^lhash,b);
B64Code.encode(length()^lhash,b);
b.append('"');
return b.toString();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Generate a properly encoded URL from a {@link File} instance. /** Generate a properly encoded URL from a {@link File} instance.
* @param file Target file. * @param file Target file.

View File

@ -0,0 +1,85 @@
//
// ========================================================================
// Copyright (c) 1995-2012 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.util;
import junit.framework.Assert;
import org.junit.Test;
public class B64CodeTest
{
String text = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.";
@Test
public void testRFC1421() throws Exception
{
String b64 = B64Code.encode(text,StringUtil.__ISO_8859_1);
Assert.assertEquals("TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz"+
"IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg"+
"dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu"+
"dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo"+
"ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=",b64);
char[] chars = B64Code.encode(text.getBytes(StringUtil.__ISO_8859_1),false);
b64 = new String(chars,0,chars.length);
Assert.assertEquals("TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz"+
"IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg"+
"dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu"+
"dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo"+
"ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=",b64);
}
@Test
public void testRFC2045() throws Exception
{
char[] chars = B64Code.encode(text.getBytes(StringUtil.__ISO_8859_1),true);
String b64 = new String(chars,0,chars.length);
Assert.assertEquals("TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\r\n"+
"IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\r\n"+
"dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\r\n"+
"dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\r\n"+
"ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=\r\n",b64);
}
@Test
public void testInteger() throws Exception
{
byte[] bytes = text.getBytes(StringUtil.__ISO_8859_1);
int value=(bytes[0]<<24)+(bytes[1]<<16)+(bytes[2]<<8)+(bytes[3]);
StringBuilder b = new StringBuilder();
B64Code.encode(value,b);
Assert.assertEquals("TWFuIA=",b.toString());
}
@Test
public void testLong() throws Exception
{
byte[] bytes = text.getBytes(StringUtil.__ISO_8859_1);
long value=((0xffL&bytes[0])<<56)+((0xffL&bytes[1])<<48)+((0xffL&bytes[2])<<40)+((0xffL&bytes[3])<<32)+
((0xffL&bytes[4])<<24)+((0xffL&bytes[5])<<16)+((0xffL&bytes[6])<<8)+(0xffL&bytes[7]);
StringBuilder b = new StringBuilder();
B64Code.encode(value,b);
Assert.assertEquals("TWFuIGlzIGQ",b.toString());
}
}

View File

@ -0,0 +1,76 @@
//
// ========================================================================
// Copyright (c) 1995-2012 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.util;
import junit.framework.Assert;
import org.junit.Test;
public class TypeUtilTest
{
@Test
public void testToHexInt() throws Exception
{
StringBuilder b = new StringBuilder();
b.setLength(0);
TypeUtil.toHex((int)0,b);
Assert.assertEquals("00000000",b.toString());
b.setLength(0);
TypeUtil.toHex(Integer.MAX_VALUE,b);
Assert.assertEquals("7FFFFFFF",b.toString());
b.setLength(0);
TypeUtil.toHex(Integer.MIN_VALUE,b);
Assert.assertEquals("80000000",b.toString());
b.setLength(0);
TypeUtil.toHex(0x12345678,b);
Assert.assertEquals("12345678",b.toString());
b.setLength(0);
TypeUtil.toHex(0x9abcdef0,b);
Assert.assertEquals("9ABCDEF0",b.toString());
}
@Test
public void testToHexLong() throws Exception
{
StringBuilder b = new StringBuilder();
b.setLength(0);
TypeUtil.toHex((long)0,b);
Assert.assertEquals("0000000000000000",b.toString());
b.setLength(0);
TypeUtil.toHex(Long.MAX_VALUE,b);
Assert.assertEquals("7FFFFFFFFFFFFFFF",b.toString());
b.setLength(0);
TypeUtil.toHex(Long.MIN_VALUE,b);
Assert.assertEquals("8000000000000000",b.toString());
b.setLength(0);
TypeUtil.toHex(0x123456789abcdef0L,b);
Assert.assertEquals("123456789ABCDEF0",b.toString());
}
}

View File

@ -85,6 +85,7 @@
<filter-mapping> <filter-mapping>
<filter-name>GzipFilter</filter-name> <filter-name>GzipFilter</filter-name>
<url-pattern>/dump/gzip/*</url-pattern> <url-pattern>/dump/gzip/*</url-pattern>
<url-pattern>*.txt</url-pattern>
</filter-mapping> </filter-mapping>