From 75aedbbf45f53576acc993096f5034c7420d1b12 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Sun, 11 Nov 2012 09:52:26 +1100 Subject: [PATCH 1/2] 393947 implement ETags and update gzip filter to handle them --- .../org/eclipse/jetty/http/HttpContent.java | 29 +++- .../http/gzip/AbstractCompressedStream.java | 98 +++++------ .../http/gzip/CompressedResponseWrapper.java | 62 ++++++- .../jetty/overlays/OverlayedAppProvider.java | 2 +- .../jetty/overlays/TemplateContext.java | 2 +- .../jetty/server/AbstractHttpConnection.java | 10 +- .../eclipse/jetty/server/ResourceCache.java | 24 +-- .../jetty/server/handler/GzipHandler.java | 4 +- .../jetty/server/handler/ResourceHandler.java | 37 ++++ .../jetty/server/ResourceCacheTest.java | 14 +- .../eclipse/jetty/servlet/DefaultServlet.java | 90 +++++++++- .../jetty/servlet/DefaultServletTest.java | 145 ++++++++++++++-- .../eclipse/jetty/servlets/GzipFilter.java | 22 ++- .../jetty/servlets/IncludableGzipFilter.java | 8 +- .../java/org/eclipse/jetty/util/B64Code.java | 160 +++++++++++++----- .../java/org/eclipse/jetty/util/TypeUtil.java | 44 ++++- .../eclipse/jetty/util/resource/Resource.java | 29 +++- .../src/main/webapp/WEB-INF/web.xml | 1 + 18 files changed, 619 insertions(+), 162 deletions(-) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java index 0b4fc43df9a..204fe2318e7 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java @@ -38,6 +38,7 @@ public interface HttpContent Buffer getLastModified(); Buffer getIndirectBuffer(); Buffer getDirectBuffer(); + Buffer getETag(); Resource getResource(); long getContentLength(); InputStream getInputStream() throws IOException; @@ -53,19 +54,33 @@ public interface HttpContent final Resource _resource; final Buffer _mimeType; final int _maxBuffer; + final Buffer _etag; + /* ------------------------------------------------------------ */ public ResourceAsHttpContent(final Resource resource, final Buffer mimeType) { - _resource=resource; - _mimeType=mimeType; - _maxBuffer=-1; + this(resource,mimeType,-1,false); } - + + /* ------------------------------------------------------------ */ 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; _mimeType=mimeType; _maxBuffer=maxBuffer; + _etag=etag?new ByteArrayBuffer(resource.getWeakETag()):null; } /* ------------------------------------------------------------ */ @@ -85,6 +100,12 @@ public interface HttpContent { return null; } + + /* ------------------------------------------------------------ */ + public Buffer getETag() + { + return _etag; + } /* ------------------------------------------------------------ */ public Buffer getIndirectBuffer() diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/AbstractCompressedStream.java b/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/AbstractCompressedStream.java index 2240f073ea5..2d918d57a2f 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/AbstractCompressedStream.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/AbstractCompressedStream.java @@ -40,43 +40,26 @@ import org.eclipse.jetty.util.ByteArrayOutputStream2; public abstract class AbstractCompressedStream extends ServletOutputStream { private final String _encoding; - protected HttpServletRequest _request; - protected HttpServletResponse _response; + protected final CompressedResponseWrapper _wrapper; + protected final HttpServletResponse _response; protected OutputStream _out; protected ByteArrayOutputStream2 _bOut; protected DeflaterOutputStream _compressedOutputStream; protected boolean _closed; - protected int _bufferSize; - protected int _minCompressSize; - protected long _contentLength; protected boolean _doNotCompress; /** * 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 { _encoding=encoding; - _request = request; - _response = response; - _contentLength = contentLength; - _bufferSize = bufferSize; - _minCompressSize = minCompressSize; - if (minCompressSize == 0) + _wrapper = wrapper; + _response = (HttpServletResponse)wrapper.getResponse(); + + if (_wrapper.getMinCompressSize()==0) doCompress(); } @@ -96,21 +79,19 @@ public abstract class AbstractCompressedStream extends ServletOutputStream _doNotCompress = false; } - /** - * Sets the content length. - * - * @param length - * the new content length - */ - public void setContentLength(long length) + /* ------------------------------------------------------------ */ + public void setContentLength() { - _contentLength = length; - if (_doNotCompress && length >= 0) + if (_doNotCompress) { - if (_contentLength < Integer.MAX_VALUE) - _response.setContentLength((int)_contentLength); - else - _response.setHeader("Content-Length",Long.toString(_contentLength)); + long length=_wrapper.getContentLength(); + if (length>=0) + { + 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 (_contentLength > 0 && _contentLength < _minCompressSize) + long length=_wrapper.getContentLength(); + if (length > 0 && length < _wrapper.getMinCompressSize()) doNotCompress(); else doCompress(); @@ -142,15 +124,19 @@ public abstract class AbstractCompressedStream extends ServletOutputStream if (_closed) return; - if (_request.getAttribute("javax.servlet.include.request_uri") != null) + if (_wrapper.getRequest().getAttribute("javax.servlet.include.request_uri") != null) flush(); else { if (_bOut != null) { - if (_contentLength < 0) - _contentLength = _bOut.getCount(); - if (_contentLength < _minCompressSize) + long length=_wrapper.getContentLength(); + if (length < 0) + { + length = _bOut.getCount(); + _wrapper.setContentLength(length); + } + if (length < _wrapper.getMinCompressSize()) doNotCompress(); else doCompress(); @@ -180,7 +166,8 @@ public abstract class AbstractCompressedStream extends ServletOutputStream { if (_out == null || _bOut != null) { - if (_contentLength > 0 && _contentLength < _minCompressSize) + long length=_wrapper.getContentLength(); + if (length > 0 && length < _wrapper.getMinCompressSize()) doNotCompress(); else doCompress(); @@ -249,6 +236,10 @@ public abstract class AbstractCompressedStream extends ServletOutputStream _out.write(_bOut.getBuf(),0,_bOut.getCount()); _bOut=null; } + + String etag=_wrapper.getETag(); + if (etag!=null) + setHeader("ETag",etag.substring(0,etag.length()-1)+'-'+_encoding+'"'); } else doNotCompress(); @@ -267,10 +258,13 @@ public abstract class AbstractCompressedStream extends ServletOutputStream throw new IllegalStateException("Compressed output stream is already assigned."); if (_out == null || _bOut != null) { + if (_wrapper.getETag()!=null) + setHeader("ETag",_wrapper.getETag()); + _doNotCompress = true; _out = _response.getOutputStream(); - setContentLength(_contentLength); + setContentLength(); if (_bOut != null) _out.write(_bOut.getBuf(),0,_bOut.getCount()); @@ -281,30 +275,32 @@ public abstract class AbstractCompressedStream extends ServletOutputStream /** * Check out. * - * @param length + * @param lengthToWrite * the length * @throws IOException * Signals that an I/O exception has occurred. */ - private void checkOut(int length) throws IOException + private void checkOut(int lengthToWrite) throws IOException { if (_closed) throw new IOException("CLOSED"); if (_out == null) { - if (_response.isCommitted() || (_contentLength >= 0 && _contentLength < _minCompressSize)) + long length=_wrapper.getContentLength(); + if (_response.isCommitted() || (length >= 0 && length < _wrapper.getMinCompressSize())) doNotCompress(); - else if (length > _minCompressSize) + else if (lengthToWrite > _wrapper.getMinCompressSize()) doCompress(); else - _out = _bOut = new ByteArrayOutputStream2(_bufferSize); + _out = _bOut = new ByteArrayOutputStream2(_wrapper.getBufferSize()); } else if (_bOut != null) { - if (_response.isCommitted() || (_contentLength >= 0 && _contentLength < _minCompressSize)) + long length=_wrapper.getContentLength(); + if (_response.isCommitted() || (length >= 0 && length < _wrapper.getMinCompressSize())) doNotCompress(); - else if (length >= (_bOut.getBuf().length - _bOut.getCount())) + else if (lengthToWrite >= (_bOut.getBuf().length - _bOut.getCount())) doCompress(); } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/CompressedResponseWrapper.java b/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/CompressedResponseWrapper.java index 2ccbd6d979d..57bd4dbf8e6 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/CompressedResponseWrapper.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/CompressedResponseWrapper.java @@ -48,15 +48,48 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp private PrintWriter _writer; private AbstractCompressedStream _compressedStream; + private String _etag; private long _contentLength=-1; private boolean _noCompression; + /* ------------------------------------------------------------ */ public CompressedResponseWrapper(HttpServletRequest request, HttpServletResponse response) { super(response); _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) @@ -148,7 +181,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp { _contentLength=length; if (_compressedStream!=null) - _compressedStream.setContentLength(length); + _compressedStream.setContentLength(); else if (_noCompression && _contentLength>=0) { HttpServletResponse response = (HttpServletResponse)getResponse(); @@ -162,7 +195,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp } } } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#addHeader(java.lang.String, java.lang.String) @@ -174,7 +207,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp { _contentLength=Long.parseLong(value); if (_compressedStream!=null) - _compressedStream.setContentLength(_contentLength); + _compressedStream.setContentLength(); } else if ("content-type".equalsIgnoreCase(name)) { @@ -188,6 +221,8 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp noCompression(); } } + else if ("etag".equalsIgnoreCase(name)) + _etag=value; else super.addHeader(name,value); } @@ -325,10 +360,21 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp noCompression(); } } + else if ("etag".equalsIgnoreCase(name)) + _etag=value; else 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() @@ -344,7 +390,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp return getResponse().getOutputStream(); } - _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minCompressSize); + _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse()); } else if (_writer!=null) throw new IllegalStateException("getWriter() called"); @@ -370,7 +416,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp return getResponse().getWriter(); } - _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minCompressSize); + _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse()); _writer=newWriter(_compressedStream,getCharacterEncoding()); } return _writer; @@ -387,7 +433,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp { _contentLength=value; if (_compressedStream!=null) - _compressedStream.setContentLength(_contentLength); + _compressedStream.setContentLength(); } else super.setIntHeader(name,value); @@ -411,6 +457,6 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp /** *@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; } \ No newline at end of file diff --git a/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/OverlayedAppProvider.java b/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/OverlayedAppProvider.java index 0a4cfb52cd4..69a8660ca29 100644 --- a/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/OverlayedAppProvider.java +++ b/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/OverlayedAppProvider.java @@ -431,7 +431,7 @@ public class OverlayedAppProvider extends AbstractLifeCycle implements AppProvid context.setBaseResource(new ResourceCollection(instance_webapp,shared.getBaseResource())); // 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); } else diff --git a/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/TemplateContext.java b/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/TemplateContext.java index 488d8f73d6d..9d311adcfaa 100644 --- a/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/TemplateContext.java +++ b/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/TemplateContext.java @@ -83,7 +83,7 @@ public class TemplateContext extends AggregateLifeCycle implements WebAppClassLo _server=server; _baseResource=baseResource; _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); _serverClasses=new ClasspathPattern(patterns==null?WebAppContext.__dftServerClasses:patterns); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpConnection.java index b2ad0cc0557..379906b12da 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpConnection.java @@ -1105,7 +1105,7 @@ public abstract class AbstractHttpConnection extends AbstractConnection if (super._generator.isWritten()) throw new IllegalStateException("!empty"); - // Convert HTTP content to contentl + // Convert HTTP content to content if (content instanceof HttpContent) { HttpContent httpContent = (HttpContent) content; @@ -1140,13 +1140,20 @@ public abstract class AbstractHttpConnection extends AbstractConnection Buffer lm = httpContent.getLastModified(); long lml=httpContent.getResource().lastModified(); if (lm != null) + { _responseFields.put(HttpHeaders.LAST_MODIFIED_BUFFER, lm); + } else if (httpContent.getResource()!=null) { if (lml!=-1) _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); content = direct?httpContent.getDirectBuffer():httpContent.getIndirectBuffer(); if (content==null) @@ -1194,7 +1201,6 @@ public abstract class AbstractHttpConnection extends AbstractConnection resource.release(); else in.close(); - } } else diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java index cde09f7d944..5577c55879e 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java @@ -58,24 +58,18 @@ public class ResourceCache private final ResourceFactory _factory; private final ResourceCache _parent; private final MimeTypes _mimeTypes; + private final boolean _etags; private boolean _useFileMappedBuffer=true; private int _maxCachedFileSize =4*1024*1024; private int _maxCachedFiles=2048; private int _maxCacheSize =32*1024*1024; - - /* ------------------------------------------------------------ */ - public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer) - { - this(parent,factory,mimeTypes); - setUseFileMappedBuffer(useFileMappedBuffer); - } /* ------------------------------------------------------------ */ /** Constructor. * @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; _cache=new ConcurrentHashMap(); @@ -83,6 +77,7 @@ public class ResourceCache _cachedFiles=new AtomicInteger(); _mimeTypes=mimeTypes; _parent=parent; + _etags=etags; } /* ------------------------------------------------------------ */ @@ -249,7 +244,7 @@ public class ResourceCache 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 Buffer _lastModifiedBytes; final Buffer _contentType; + final Buffer _etagBuffer; volatile long _lastAccessed; AtomicReference _indirectBuffer=new AtomicReference(); @@ -381,6 +377,8 @@ public class ResourceCache _cachedSize.addAndGet(_length); _cachedFiles.incrementAndGet(); _lastAccessed=System.currentTimeMillis(); + + _etagBuffer=_etags?new ByteArrayBuffer(resource.getWeakETag()):null; } @@ -407,11 +405,17 @@ public class ResourceCache { return _resource; } + + /* ------------------------------------------------------------ */ + public Buffer getETag() + { + return _etagBuffer; + } /* ------------------------------------------------------------ */ boolean isValid() { - if (_lastModified==_resource.lastModified()) + if (_lastModified==_resource.lastModified() && _length==_resource.length()) { _lastAccessed=System.currentTimeMillis(); return true; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/GzipHandler.java index 7c0979aca92..d43d8656533 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/GzipHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/GzipHandler.java @@ -295,9 +295,9 @@ public class GzipHandler extends HandlerWrapper } @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 protected DeflaterOutputStream createStream() throws IOException diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java index 49eccb3a036..d2e0ac4465e 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java @@ -69,6 +69,7 @@ public class ResourceHandler extends HandlerWrapper ByteArrayBuffer _cacheControl; boolean _aliases; boolean _directory; + boolean _etags; /* ------------------------------------------------------------ */ public ResourceHandler() @@ -128,6 +129,24 @@ public class ResourceHandler extends HandlerWrapper _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 public void doStart() @@ -419,6 +438,21 @@ public class ResourceHandler extends HandlerWrapper // set some headers 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) { long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE); @@ -436,6 +470,9 @@ public class ResourceHandler extends HandlerWrapper // set the headers doResponseHeaders(response,resource,mime!=null?mime.toString():null); response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified); + if (_etags) + baseRequest.getResponse().getHttpFields().put(HttpHeaders.ETAG_BUFFER,etag); + if(skipContentBody) return; // Send the content diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java index 3d47bdf28c8..875bc653ce6 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java @@ -46,9 +46,9 @@ public class ResourceCacheTest Resource[] r = rc.getResources(); MimeTypes mime = new MimeTypes(); - ResourceCache rc3 = new ResourceCache(null,r[2],mime,false); - ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false); - ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false); + ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false); + ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false); + ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false,false); assertEquals("1 - one", getContent(rc1, "1.txt")); assertEquals("2 - two", getContent(rc1, "2.txt")); @@ -76,8 +76,8 @@ public class ResourceCacheTest Resource[] r = rc.getResources(); MimeTypes mime = new MimeTypes(); - ResourceCache rc3 = new ResourceCache(null,r[2],mime,false); - ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false) + ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false); + ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false) { @Override 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("2 - two", getContent(rc1, "2.txt")); @@ -126,7 +126,7 @@ public class ResourceCacheTest 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.setMaxCachedFileSize(85); diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java index c5b91cacc9d..11fac730bc3 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -58,6 +58,7 @@ import org.eclipse.jetty.server.nio.NIOConnector; import org.eclipse.jetty.server.ssl.SslConnector; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.MultiPartOutputStream; +import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -116,6 +117,8 @@ import org.eclipse.jetty.util.resource.ResourceFactory; * * aliases If True, aliases of resources are allowed (eg. symbolic * 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. * maxCachedFileSize The maximum size of a file to cache @@ -152,6 +155,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory private boolean _redirectWelcome=false; private boolean _gzip=true; private boolean _pathInfoOnly=false; + private boolean _etags=false; private Resource _resourceBase; private ResourceCache _cache; @@ -262,11 +266,13 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory LOG.debug("Cache {}={}",resourceCache,_cache); } + _etags = getInitBoolean("etags",_etags); + try { 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) _cache.setMaxCacheSize(max_cache_size); @@ -287,6 +293,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory if (h.getServletInstance()==this) _defaultHolder=h; + if (LOG.isDebugEnabled()) LOG.debug("resource base = "+_resourceBase); } @@ -499,7 +506,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory { // ensure we have content 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)) { @@ -570,7 +577,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory } 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)) sendDirectory(request,response,resource,pathInContext); } @@ -672,6 +679,75 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory { 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); if (ifms!=null) { @@ -969,7 +1045,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory /* ------------------------------------------------------------ */ protected void writeHeaders(HttpServletResponse response,HttpContent content,long count) throws IOException - { + { if (content.getContentType()!=null && response.getContentType()==null) response.setContentType(content.getContentType().toString()); @@ -991,6 +1067,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory r.setLongContentLength(count); writeOptionHeaders(fields); + + if (_etags) + fields.put(HttpHeaders.ETAG_BUFFER,content.getETag()); } else { @@ -1007,6 +1086,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory } writeOptionHeaders(response); + + if (_etags) + response.setHeader(HttpHeaders.ETAG,content.getETag().toString()); } } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java index fae6e0bfd1a..31eda5958e9 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java @@ -23,6 +23,8 @@ import static org.junit.Assert.assertTrue; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -33,14 +35,17 @@ import javax.servlet.ServletResponse; import junit.framework.AssertionFailedError; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.toolchain.test.TestingDir; +import org.eclipse.jetty.util.DateCache; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -578,6 +583,123 @@ public class DefaultServletTest 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 void init(FilterConfig filterConfig) throws ServletException @@ -644,18 +766,8 @@ public class DefaultServletTest private int assertResponseContains(String expected, String response) { - int idx = response.indexOf(expected); - if (idx == (-1)) - { - // 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; + Assert.assertThat(response,Matchers.containsString(expected)); + return response.indexOf(expected); } private void deleteFile(File file) throws IOException @@ -680,4 +792,13 @@ public class DefaultServletTest 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; + } } diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java index 27c930b2045..352faf22fcb 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java @@ -106,8 +106,10 @@ public class GzipFilter extends UserAgentFilter { private static final Logger LOG = Log.getLogger(GzipFilter.class); public final static String GZIP="gzip"; + public final static String ETAG_GZIP="-gzip\""; 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 _mimeTypes; protected int _bufferSize=8192; @@ -235,6 +237,16 @@ public class GzipFilter extends UserAgentFilter 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); boolean exceptional=true; @@ -362,9 +374,9 @@ public class GzipFilter extends UserAgentFilter wrappedResponse = new CompressedResponseWrapper(request,response) { @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 protected DeflaterOutputStream createStream() throws IOException @@ -380,9 +392,9 @@ public class GzipFilter extends UserAgentFilter wrappedResponse = new CompressedResponseWrapper(request,response) { @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 protected DeflaterOutputStream createStream() throws IOException diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java index c589d5c4419..6f23128efab 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java @@ -75,9 +75,9 @@ public class IncludableGzipFilter extends GzipFilter wrappedResponse = new IncludableResponseWrapper(request,response) { @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 protected DeflaterOutputStream createStream() throws IOException @@ -102,9 +102,9 @@ public class IncludableGzipFilter extends GzipFilter wrappedResponse = new IncludableResponseWrapper(request,response) { @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 protected DeflaterOutputStream createStream() throws IOException diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java b/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java index d34deacf634..d9ca17c84ac 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.util; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -33,8 +34,8 @@ import java.io.UnsupportedEncodingException; public class B64Code { // ------------------------------------------------------------------ - static final char pad='='; - static final char[] rfc1421alphabet= + static final char __pad='='; + static final char[] __rfc1421alphabet= { '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', @@ -42,16 +43,16 @@ public class B64Code 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' }; - static final byte[] rfc1421nibbles; + static final byte[] __rfc1421nibbles; static { - rfc1421nibbles=new byte[256]; + __rfc1421nibbles=new byte[256]; for (int i=0;i<256;i++) - rfc1421nibbles[i]=-1; + __rfc1421nibbles[i]=-1; for (byte b=0;b<64;b++) - rfc1421nibbles[(byte)rfc1421alphabet[b]]=b; - rfc1421nibbles[(byte)pad]=0; + __rfc1421nibbles[(byte)__rfc1421alphabet[b]]=b; + __rfc1421nibbles[(byte)__pad]=0; } // ------------------------------------------------------------------ @@ -104,7 +105,54 @@ public class B64Code */ 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>>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) return null; + if (!rfc2045) + return encode(b); int bLen=b.length; int cLen=((bLen+2)/3)*4; - if (rfc2045) - cLen+=2+2*cLen/76; + cLen+=2+2*(cLen/76); char c[]=new char[cLen]; int ci=0; int bi=0; @@ -136,12 +185,12 @@ public class B64Code 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]; + 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]; l+=4; - if (rfc2045 && l%76==0) + if (l%76==0) { c[ci++]=13; c[ci++]=10; @@ -155,18 +204,18 @@ public class B64Code 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; + 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; + c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f]; + c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f]; + c[ci++]=__pad; + c[ci++]=__pad; break; default: @@ -174,11 +223,8 @@ public class B64Code } } - if (rfc2045) - { - c[ci++]=13; - c[ci++]=10; - } + c[ci++]=13; + c[ci++]=10; return c; } @@ -226,7 +272,7 @@ public class B64Code throw new IllegalArgumentException("Input block size is not 4"); int li=bLen-1; - while (li>=0 && b[li]==(byte)pad) + while (li>=0 && b[li]==(byte)__pad) li--; if (li<0) @@ -243,10 +289,10 @@ public class B64Code { while (ri>>4); @@ -270,8 +316,8 @@ public class B64Code break; case 1: - b0=rfc1421nibbles[b[bi++]]; - b1=rfc1421nibbles[b[bi++]]; + b0=__rfc1421nibbles[b[bi++]]; + b1=__rfc1421nibbles[b[bi++]]; if (b0<0 || b1<0) throw new IllegalArgumentException("Not B64 encoded"); r[ri++]=(byte)(b0<<2|b1>>>4); @@ -314,17 +360,17 @@ public class B64Code { char c=encoded.charAt(ci++); - if (c==pad) + if (c==__pad) break; if (Character.isWhitespace(c)) continue; - byte nibble=rfc1421nibbles[c]; + byte nibble=__rfc1421nibbles[c]; if (nibble<0) throw new IllegalArgumentException("Not B64 encoded"); - nibbles[s++]=rfc1421nibbles[c]; + nibbles[s++]=__rfc1421nibbles[c]; switch(s) { @@ -346,4 +392,36 @@ public class B64Code 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)]); + } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java index 6246bc104b1..5bc4ec19e92 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java @@ -374,15 +374,10 @@ public class TypeUtil { try { - int bi=0xff&b; - int c='0'+(bi/16)%16; - if (c>'9') - c= 'A'+(c-'0'-10); - buf.append((char)c); - c='0'+bi%16; - if (c>'9') - c= 'A'+(c-'0'-10); - buf.append((char)c); + int d=0xf&((0xF0&b)>>4); + buf.append((char)((d>9?('A'-10):'0')+d)); + d=0xf&b; + buf.append((char)((d>9?('A'-10):'0')+d)); } 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) { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java index bc13427e07e..90944a1cb72 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java @@ -31,6 +31,7 @@ import java.text.DateFormat; import java.util.Arrays; import java.util.Date; +import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.StringUtil; @@ -48,7 +49,8 @@ public abstract class Resource implements ResourceFactory private static final Logger LOG = Log.getLogger(Resource.class); public static boolean __defaultUseCaches = true; volatile Object _associate; - + + /* ------------------------------------------------------------ */ /** * Change the default setting for url connection caches. * Subsequent URLConnections will use this default. @@ -662,6 +664,31 @@ public abstract class Resource implements ResourceFactory 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 GzipFilter /dump/gzip/* + *.txt From 0f60eb751d64b509feaed113383fde86e71613e0 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Sun, 11 Nov 2012 10:25:10 +1100 Subject: [PATCH 2/2] 393947 additional tests --- .../org/eclipse/jetty/util/B64CodeTest.java | 85 +++++++++++++++++++ .../org/eclipse/jetty/util/TypeUtilTest.java | 76 +++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/B64CodeTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/TypeUtilTest.java diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/B64CodeTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/B64CodeTest.java new file mode 100644 index 00000000000..c3113043103 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/B64CodeTest.java @@ -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()); + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/TypeUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/TypeUtilTest.java new file mode 100644 index 00000000000..265da9c9339 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/TypeUtilTest.java @@ -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()); + } + +}