484622 - Improve handling of Direct and Mapped buffers for static content

ResourceHttpContent now applies a maxBufferSize that is passed through the call to getContent
ResourceCache now accounts for the exact memory usage of content, which may have an indirect
buffer plus either a direct or mapped buffer.  Thus content size may be 0, 1 or 2 times the
file size.

Some more limited unit tests
This commit is contained in:
Greg Wilkins 2015-12-18 12:36:27 +11:00
parent ecbfe7c1d0
commit 5cd676581c
11 changed files with 262 additions and 76 deletions

View File

@ -68,6 +68,13 @@ public interface HttpContent
public interface Factory public interface Factory
{ {
HttpContent getContent(String path) throws IOException; /**
* @param path The path within the context to the resource
* @param maxBuffer The maximum buffer to allocated for this request. For cached content, a larger buffer may have
* previously been allocated and returned by the {@link HttpContent#getDirectBuffer()} or {@link HttpContent#getIndirectBuffer()} calls.
* @return A {@link HttpContent}
* @throws IOException
*/
HttpContent getContent(String path,int maxBuffer) throws IOException;
} }
} }

View File

@ -125,7 +125,7 @@ public class ResourceHttpContent implements HttpContent
@Override @Override
public ByteBuffer getDirectBuffer() public ByteBuffer getDirectBuffer()
{ {
if (_resource.length()<=0 || _maxBuffer<_resource.length()) if (_resource.length()<=0 || _maxBuffer>0 && _maxBuffer<_resource.length())
return null; return null;
try try
{ {
@ -155,7 +155,7 @@ public class ResourceHttpContent implements HttpContent
@Override @Override
public ByteBuffer getIndirectBuffer() public ByteBuffer getIndirectBuffer()
{ {
if (_resource.length()<=0 || _maxBuffer<_resource.length()) if (_resource.length()<=0 || _maxBuffer>0 && _maxBuffer<_resource.length())
return null; return null;
try try
{ {

View File

@ -765,6 +765,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
} }
break; break;
} }
ByteBuffer buffer = _channel.useDirectBuffers() ? httpContent.getDirectBuffer() : null; ByteBuffer buffer = _channel.useDirectBuffers() ? httpContent.getDirectBuffer() : null;
if (buffer == null) if (buffer == null)

View File

@ -22,6 +22,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.util.Comparator; import java.util.Comparator;
import java.util.SortedSet; import java.util.SortedSet;
@ -172,7 +173,7 @@ public class ResourceCache implements HttpContent.Factory
public HttpContent lookup(String pathInContext) public HttpContent lookup(String pathInContext)
throws IOException throws IOException
{ {
return getContent(pathInContext); return getContent(pathInContext,_maxCachedFileSize);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -180,6 +181,8 @@ public class ResourceCache implements HttpContent.Factory
* Get either a valid entry object or create a new one if possible. * Get either a valid entry object or create a new one if possible.
* *
* @param pathInContext The key into the cache * @param pathInContext The key into the cache
* @param maxBuffer The maximum buffer to allocated for this request. For cached content, a larger buffer may have
* previously been allocated and returned by the {@link HttpContent#getDirectBuffer()} or {@link HttpContent#getIndirectBuffer()} calls.
* @return The entry matching <code>pathInContext</code>, or a new entry * @return The entry matching <code>pathInContext</code>, or a new entry
* if no matching entry was found. If the content exists but is not cachable, * if no matching entry was found. If the content exists but is not cachable,
* then a {@link ResourceHttpContent} instance is return. If * then a {@link ResourceHttpContent} instance is return. If
@ -187,7 +190,7 @@ public class ResourceCache implements HttpContent.Factory
* @throws IOException Problem loading the resource * @throws IOException Problem loading the resource
*/ */
@Override @Override
public HttpContent getContent(String pathInContext) public HttpContent getContent(String pathInContext,int maxBufferSize)
throws IOException throws IOException
{ {
// Is the content in this cache? // Is the content in this cache?
@ -197,14 +200,14 @@ public class ResourceCache implements HttpContent.Factory
// try loading the content from our factory. // try loading the content from our factory.
Resource resource=_factory.getResource(pathInContext); Resource resource=_factory.getResource(pathInContext);
HttpContent loaded = load(pathInContext,resource); HttpContent loaded = load(pathInContext,resource,maxBufferSize);
if (loaded!=null) if (loaded!=null)
return loaded; return loaded;
// Is the content in the parent cache? // Is the content in the parent cache?
if (_parent!=null) if (_parent!=null)
{ {
HttpContent httpContent=_parent.lookup(pathInContext); HttpContent httpContent=_parent.getContent(pathInContext,maxBufferSize);
if (httpContent!=null) if (httpContent!=null)
return httpContent; return httpContent;
} }
@ -225,14 +228,13 @@ public class ResourceCache implements HttpContent.Factory
long len = resource.length(); long len = resource.length();
// Will it fit in the cache? // Will it fit in the cache?
return (len>0 && len<_maxCachedFileSize && len<_maxCacheSize); return (len>0 && (_useFileMappedBuffer || (len<_maxCachedFileSize && len<_maxCacheSize)));
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
private HttpContent load(String pathInContext, Resource resource) private HttpContent load(String pathInContext, Resource resource, int maxBufferSize)
throws IOException throws IOException
{ {
if (resource==null || !resource.exists()) if (resource==null || !resource.exists())
return null; return null;
@ -256,7 +258,6 @@ public class ResourceCache implements HttpContent.Factory
if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length()) if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length())
{ {
contentGz = new CachedHttpContent(pathInContextGz,resourceGz,null); contentGz = new CachedHttpContent(pathInContextGz,resourceGz,null);
shrinkCache();
CachedHttpContent added = _cache.putIfAbsent(pathInContextGz,contentGz); CachedHttpContent added = _cache.putIfAbsent(pathInContextGz,contentGz);
if (added!=null) if (added!=null)
{ {
@ -270,9 +271,6 @@ public class ResourceCache implements HttpContent.Factory
else else
content = new CachedHttpContent(pathInContext,resource,null); content = new CachedHttpContent(pathInContext,resource,null);
// reduce the cache to an acceptable size.
shrinkCache();
// Add it to the cache. // Add it to the cache.
CachedHttpContent added = _cache.putIfAbsent(pathInContext,content); CachedHttpContent added = _cache.putIfAbsent(pathInContext,content);
if (added!=null) if (added!=null)
@ -284,7 +282,7 @@ public class ResourceCache implements HttpContent.Factory
return content; return content;
} }
// Look for a gzip resource or content // Look for non Cacheable gzip resource or content
String mt = _mimeTypes.getMimeByExtension(pathInContext); String mt = _mimeTypes.getMimeByExtension(pathInContext);
if (_gzip) if (_gzip)
{ {
@ -292,16 +290,16 @@ public class ResourceCache implements HttpContent.Factory
String pathInContextGz=pathInContext+".gz"; String pathInContextGz=pathInContext+".gz";
CachedHttpContent contentGz = _cache.get(pathInContextGz); CachedHttpContent contentGz = _cache.get(pathInContextGz);
if (contentGz!=null && contentGz.isValid() && contentGz.getResource().lastModified()>=resource.lastModified()) if (contentGz!=null && contentGz.isValid() && contentGz.getResource().lastModified()>=resource.lastModified())
return new ResourceHttpContent(resource,mt,getMaxCachedFileSize(),contentGz); return new ResourceHttpContent(resource,mt,maxBufferSize,contentGz);
// Is there a gzip resource? // Is there a gzip resource?
Resource resourceGz=_factory.getResource(pathInContextGz); Resource resourceGz=_factory.getResource(pathInContextGz);
if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length()) if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length())
return new ResourceHttpContent(resource,mt,getMaxCachedFileSize(), return new ResourceHttpContent(resource,mt,maxBufferSize,
new ResourceHttpContent(resourceGz,_mimeTypes.getMimeByExtension(pathInContextGz),getMaxCachedFileSize())); new ResourceHttpContent(resourceGz,_mimeTypes.getMimeByExtension(pathInContextGz),maxBufferSize));
} }
return new ResourceHttpContent(resource,mt,getMaxCachedFileSize()); return new ResourceHttpContent(resource,mt,maxBufferSize);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -359,6 +357,8 @@ public class ResourceCache implements HttpContent.Factory
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
protected ByteBuffer getDirectBuffer(Resource resource) protected ByteBuffer getDirectBuffer(Resource resource)
{ {
// Only use file mapped buffers for cached resources, otherwise too much virtual memory commitment for
// a non shared resource. Also ignore max buffer size
try try
{ {
if (_useFileMappedBuffer && resource.getFile()!=null && resource.length()<Integer.MAX_VALUE) if (_useFileMappedBuffer && resource.getFile()!=null && resource.length()<Integer.MAX_VALUE)
@ -421,8 +421,9 @@ public class ResourceCache implements HttpContent.Factory
_contentLengthValue=exists?(int)resource.length():0; _contentLengthValue=exists?(int)resource.length():0;
_contentLength=new PreEncodedHttpField(HttpHeader.CONTENT_LENGTH,Long.toString(_contentLengthValue)); _contentLength=new PreEncodedHttpField(HttpHeader.CONTENT_LENGTH,Long.toString(_contentLengthValue));
_cachedSize.addAndGet(_contentLengthValue); if (_cachedFiles.incrementAndGet()>_maxCachedFiles)
_cachedFiles.incrementAndGet(); shrinkCache();
_lastAccessed=System.currentTimeMillis(); _lastAccessed=System.currentTimeMillis();
_etag=ResourceCache.this._etags?new PreEncodedHttpField(HttpHeader.ETAG,resource.getWeakETag()):null; _etag=ResourceCache.this._etags?new PreEncodedHttpField(HttpHeader.ETAG,resource.getWeakETag()):null;
@ -487,8 +488,14 @@ public class ResourceCache implements HttpContent.Factory
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
protected void invalidate() protected void invalidate()
{ {
// Invalidate it ByteBuffer indirect=_indirectBuffer.get();
_cachedSize.addAndGet(-_contentLengthValue); if (indirect!=null && _indirectBuffer.compareAndSet(indirect,null))
_cachedSize.addAndGet(-BufferUtil.length(indirect));
ByteBuffer direct=_directBuffer.get();
if (direct!=null && !BufferUtil.isMappedBuffer(direct) && _directBuffer.compareAndSet(direct,null))
_cachedSize.addAndGet(-BufferUtil.length(direct));
_cachedFiles.decrementAndGet(); _cachedFiles.decrementAndGet();
_resource.close(); _resource.close();
} }
@ -507,7 +514,6 @@ public class ResourceCache implements HttpContent.Factory
return _lastModified==null?null:_lastModified.getValue(); return _lastModified==null?null:_lastModified.getValue();
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@Override @Override
public HttpField getContentType() public HttpField getContentType()
@ -569,7 +575,11 @@ public class ResourceCache implements HttpContent.Factory
if (buffer2==null) if (buffer2==null)
LOG.warn("Could not load "+this); LOG.warn("Could not load "+this);
else if (_indirectBuffer.compareAndSet(null,buffer2)) else if (_indirectBuffer.compareAndSet(null,buffer2))
{
buffer=buffer2; buffer=buffer2;
if (_cachedSize.addAndGet(BufferUtil.length(buffer))>_maxCacheSize)
shrinkCache();
}
else else
buffer=_indirectBuffer.get(); buffer=_indirectBuffer.get();
} }
@ -578,7 +588,6 @@ public class ResourceCache implements HttpContent.Factory
return buffer.slice(); return buffer.slice();
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@Override @Override
public ByteBuffer getDirectBuffer() public ByteBuffer getDirectBuffer()
@ -591,7 +600,12 @@ public class ResourceCache implements HttpContent.Factory
if (buffer2==null) if (buffer2==null)
LOG.warn("Could not load "+this); LOG.warn("Could not load "+this);
else if (_directBuffer.compareAndSet(null,buffer2)) else if (_directBuffer.compareAndSet(null,buffer2))
{
buffer=buffer2; buffer=buffer2;
if (!BufferUtil.isMappedBuffer(buffer) && _cachedSize.addAndGet(BufferUtil.length(buffer))>_maxCacheSize)
shrinkCache();
}
else else
buffer=_directBuffer.get(); buffer=_directBuffer.get();
} }

View File

@ -27,55 +27,47 @@ import org.eclipse.jetty.http.ResourceHttpContent;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory; import org.eclipse.jetty.util.resource.ResourceFactory;
/**
* A HttpContent.Factory for transient content. The HttpContent's created by
* this factory are not intended to be cached, so memory limits for individual
* HttpOutput streams are enforced.
*/
public class ResourceContentFactory implements Factory public class ResourceContentFactory implements Factory
{ {
private final ResourceFactory _factory; private final ResourceFactory _factory;
private final MimeTypes _mimeTypes; private final MimeTypes _mimeTypes;
private final int _maxBufferSize;
private final boolean _gzip; private final boolean _gzip;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public ResourceContentFactory(ResourceFactory factory, MimeTypes mimeTypes, int maxBufferSize, boolean gzip) public ResourceContentFactory(ResourceFactory factory, MimeTypes mimeTypes, boolean gzip)
{ {
_factory=factory; _factory=factory;
_mimeTypes=mimeTypes; _mimeTypes=mimeTypes;
_maxBufferSize=maxBufferSize;
_gzip=gzip; _gzip=gzip;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Get a Entry from the cache.
* Get either a valid entry object or create a new one if possible.
*
* @param pathInContext The key into the cache
* @return The entry matching <code>pathInContext</code>, or a new entry
* if no matching entry was found. If the content exists but is not cachable,
* then a {@link ResourceHttpContent} instance is return. If
* the resource does not exist, then null is returned.
* @throws IOException Problem loading the resource
*/
@Override @Override
public HttpContent getContent(String pathInContext) public HttpContent getContent(String pathInContext,int maxBufferSize)
throws IOException throws IOException
{ {
// try loading the content from our factory. // try loading the content from our factory.
Resource resource=_factory.getResource(pathInContext); Resource resource=_factory.getResource(pathInContext);
HttpContent loaded = load(pathInContext,resource); HttpContent loaded = load(pathInContext,resource,maxBufferSize);
return loaded; return loaded;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
private HttpContent load(String pathInContext, Resource resource) private HttpContent load(String pathInContext, Resource resource, int maxBufferSize)
throws IOException throws IOException
{ {
if (resource==null || !resource.exists()) if (resource==null || !resource.exists())
return null; return null;
if (resource.isDirectory()) if (resource.isDirectory())
return new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),_maxBufferSize); return new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),maxBufferSize);
// Look for a gzip resource or content // Look for a gzip resource or content
String mt = _mimeTypes.getMimeByExtension(pathInContext); String mt = _mimeTypes.getMimeByExtension(pathInContext);
@ -85,11 +77,11 @@ public class ResourceContentFactory implements Factory
String pathInContextGz=pathInContext+".gz"; String pathInContextGz=pathInContext+".gz";
Resource resourceGz=_factory.getResource(pathInContextGz); Resource resourceGz=_factory.getResource(pathInContextGz);
if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length()) if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length())
return new ResourceHttpContent(resource,mt,_maxBufferSize, return new ResourceHttpContent(resource,mt,maxBufferSize,
new ResourceHttpContent(resourceGz,_mimeTypes.getMimeByExtension(pathInContextGz),_maxBufferSize)); new ResourceHttpContent(resourceGz,_mimeTypes.getMimeByExtension(pathInContextGz),maxBufferSize));
} }
return new ResourceHttpContent(resource,mt,_maxBufferSize); return new ResourceHttpContent(resource,mt,maxBufferSize);
} }

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.server;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@ -30,6 +31,7 @@ import java.io.OutputStream;
import org.eclipse.jetty.http.HttpContent; import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.ResourceHttpContent; import org.eclipse.jetty.http.ResourceHttpContent;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection; import org.eclipse.jetty.util.resource.ResourceCollection;
@ -136,50 +138,83 @@ public class ResourceCacheTest
cache.setMaxCachedFileSize(85); cache.setMaxCachedFileSize(85);
cache.setMaxCachedFiles(4); cache.setMaxCachedFiles(4);
assertTrue(cache.lookup("does not exist")==null); assertTrue(cache.getContent("does not exist",4096)==null);
assertTrue(cache.lookup(names[9]) instanceof ResourceHttpContent); assertTrue(cache.getContent(names[9],4096) instanceof ResourceHttpContent);
assertTrue(cache.getContent(names[9],4096).getIndirectBuffer()!=null);
HttpContent content; HttpContent content;
content=cache.lookup(names[8]); content=cache.getContent(names[8],4096);
assertTrue(content!=null); assertTrue(content!=null);
assertEquals(80,content.getContentLengthValue()); assertEquals(80,content.getContentLengthValue());
assertEquals(0,cache.getCachedSize());
if (OS.IS_LINUX)
{
// Initially not using memory mapped files
content.getDirectBuffer();
assertEquals(80,cache.getCachedSize());
// with both types of buffer loaded, this is too large for cache
content.getIndirectBuffer();
assertEquals(0,cache.getCachedSize());
assertEquals(0,cache.getCachedFiles());
cache=new ResourceCache(null,directory,new MimeTypes(),true,false,false);
cache.setMaxCacheSize(95);
cache.setMaxCachedFileSize(85);
cache.setMaxCachedFiles(4);
content=cache.getContent(names[8],4096);
content.getDirectBuffer();
assertEquals(cache.isUseFileMappedBuffer()?0:80,cache.getCachedSize());
// with both types of buffer loaded, this is not too large for cache because
// mapped buffers don't count, so we can continue
}
content.getIndirectBuffer();
assertEquals(80,cache.getCachedSize()); assertEquals(80,cache.getCachedSize());
assertEquals(1,cache.getCachedFiles()); assertEquals(1,cache.getCachedFiles());
Thread.sleep(200); Thread.sleep(200);
content=cache.lookup(names[1]); content=cache.getContent(names[1],4096);
assertEquals(80,cache.getCachedSize());
content.getIndirectBuffer();
assertEquals(90,cache.getCachedSize()); assertEquals(90,cache.getCachedSize());
assertEquals(2,cache.getCachedFiles()); assertEquals(2,cache.getCachedFiles());
Thread.sleep(200); Thread.sleep(200);
content=cache.lookup(names[2]); content=cache.getContent(names[2],4096);
content.getIndirectBuffer();
assertEquals(30,cache.getCachedSize()); assertEquals(30,cache.getCachedSize());
assertEquals(2,cache.getCachedFiles()); assertEquals(2,cache.getCachedFiles());
Thread.sleep(200); Thread.sleep(200);
content=cache.lookup(names[3]); content=cache.getContent(names[3],4096);
content.getIndirectBuffer();
assertEquals(60,cache.getCachedSize()); assertEquals(60,cache.getCachedSize());
assertEquals(3,cache.getCachedFiles()); assertEquals(3,cache.getCachedFiles());
Thread.sleep(200); Thread.sleep(200);
content=cache.lookup(names[4]); content=cache.getContent(names[4],4096);
content.getIndirectBuffer();
assertEquals(90,cache.getCachedSize()); assertEquals(90,cache.getCachedSize());
assertEquals(3,cache.getCachedFiles()); assertEquals(3,cache.getCachedFiles());
Thread.sleep(200); Thread.sleep(200);
content=cache.lookup(names[5]); content=cache.getContent(names[5],4096);
content.getIndirectBuffer();
assertEquals(90,cache.getCachedSize()); assertEquals(90,cache.getCachedSize());
assertEquals(2,cache.getCachedFiles()); assertEquals(2,cache.getCachedFiles());
Thread.sleep(200); Thread.sleep(200);
content=cache.lookup(names[6]); content=cache.getContent(names[6],4096);
content.getIndirectBuffer();
assertEquals(60,cache.getCachedSize()); assertEquals(60,cache.getCachedSize());
assertEquals(1,cache.getCachedFiles()); assertEquals(1,cache.getCachedFiles());
@ -189,37 +224,43 @@ public class ResourceCacheTest
{ {
out.write(' '); out.write(' ');
} }
content=cache.lookup(names[7]); content=cache.getContent(names[7],4096);
content.getIndirectBuffer();
assertEquals(70,cache.getCachedSize()); assertEquals(70,cache.getCachedSize());
assertEquals(1,cache.getCachedFiles()); assertEquals(1,cache.getCachedFiles());
Thread.sleep(200); Thread.sleep(200);
content=cache.lookup(names[6]); content=cache.getContent(names[6],4096);
content.getIndirectBuffer();
assertEquals(71,cache.getCachedSize()); assertEquals(71,cache.getCachedSize());
assertEquals(2,cache.getCachedFiles()); assertEquals(2,cache.getCachedFiles());
Thread.sleep(200); Thread.sleep(200);
content=cache.lookup(names[0]); content=cache.getContent(names[0],4096);
content.getIndirectBuffer();
assertEquals(72,cache.getCachedSize()); assertEquals(72,cache.getCachedSize());
assertEquals(3,cache.getCachedFiles()); assertEquals(3,cache.getCachedFiles());
Thread.sleep(200); Thread.sleep(200);
content=cache.lookup(names[1]); content=cache.getContent(names[1],4096);
content.getIndirectBuffer();
assertEquals(82,cache.getCachedSize()); assertEquals(82,cache.getCachedSize());
assertEquals(4,cache.getCachedFiles()); assertEquals(4,cache.getCachedFiles());
Thread.sleep(200); Thread.sleep(200);
content=cache.lookup(names[2]); content=cache.getContent(names[2],4096);
content.getIndirectBuffer();
assertEquals(32,cache.getCachedSize()); assertEquals(32,cache.getCachedSize());
assertEquals(4,cache.getCachedFiles()); assertEquals(4,cache.getCachedFiles());
Thread.sleep(200); Thread.sleep(200);
content=cache.lookup(names[3]); content=cache.getContent(names[3],4096);
content.getIndirectBuffer();
assertEquals(61,cache.getCachedSize()); assertEquals(61,cache.getCachedSize());
assertEquals(4,cache.getCachedFiles()); assertEquals(4,cache.getCachedFiles());

View File

@ -288,8 +288,15 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
throw new UnavailableException(e.toString()); throw new UnavailableException(e.toString());
} }
_contentFactory=_cache==null?new ResourceContentFactory(this,_mimeTypes,-1,_gzip):_cache; // TODO pass a buffer size if (_cache!=null)
_contentFactory=_cache;
else
{
_contentFactory=new ResourceContentFactory(this,_mimeTypes,_gzip);
if (resourceCache!=null)
_servletContext.setAttribute(resourceCache,_contentFactory);
}
_gzipEquivalentFileExtensions = new ArrayList<String>(); _gzipEquivalentFileExtensions = new ArrayList<String>();
String otherGzipExtensions = getInitParameter("otherGzipFileExtensions"); String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
if (otherGzipExtensions != null) if (otherGzipExtensions != null)
@ -460,7 +467,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
try try
{ {
// Find the content // Find the content
content=_contentFactory.getContent(pathInContext); content=_contentFactory.getContent(pathInContext,response.getBufferSize());
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.info("content={}",content); LOG.info("content={}",content);

View File

@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -39,8 +40,10 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse; import javax.servlet.ServletResponse;
import org.eclipse.jetty.http.DateGenerator; import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.ResourceContentFactory;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker; import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker;
import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.FS;
@ -48,6 +51,7 @@ 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.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.Resource;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
@ -521,6 +525,45 @@ public class DefaultServletTest
} }
} }
@Test
public void testDirectFromResourceHttpContent() throws Exception
{
if (!OS.IS_LINUX)
return;
testdir.ensureEmpty();
File resBase = testdir.getPathFile("docroot").toFile();
FS.ensureDirExists(resBase);
context.setBaseResource(Resource.newResource(resBase));
File index = new File(resBase, "index.html");
createFile(index, "<h1>Hello World</h1>");
ServletHolder defholder = context.addServlet(DefaultServlet.class, "/");
defholder.setInitParameter("dirAllowed", "true");
defholder.setInitParameter("redirectWelcome", "false");
defholder.setInitParameter("useFileMappedBuffer", "true");
defholder.setInitParameter("welcomeServlets", "exact");
defholder.setInitParameter("gzip", "false");
defholder.setInitParameter("resourceCache","resourceCache");
String response;
response = connector.getResponses("GET /context/index.html HTTP/1.0\r\n\r\n");
assertResponseContains("<h1>Hello World</h1>", response);
ResourceContentFactory factory = (ResourceContentFactory)context.getServletContext().getAttribute("resourceCache");
HttpContent content = factory.getContent("/index.html",200);
ByteBuffer buffer = content.getDirectBuffer();
Assert.assertTrue(buffer.isDirect());
content = factory.getContent("/index.html",5);
buffer = content.getDirectBuffer();
Assert.assertTrue(buffer==null);
}
@Test @Test
public void testRangeRequests() throws Exception public void testRangeRequests() throws Exception
{ {

View File

@ -1107,10 +1107,10 @@ public class DoSFilter implements Filter
{ {
private static final long serialVersionUID = 3534663738034577872L; private static final long serialVersionUID = 3534663738034577872L;
protected transient final String _id; protected final String _id;
protected transient final int _type; protected final int _type;
protected transient final long[] _timestamps; protected final long[] _timestamps;
protected transient int _next; protected int _next;
public RateTracker(String id, int type, int maxRequestsPerSecond) public RateTracker(String id, int type, int maxRequestsPerSecond)
{ {
@ -1164,16 +1164,14 @@ public class DoSFilter implements Filter
public void sessionWillPassivate(HttpSessionEvent se) public void sessionWillPassivate(HttpSessionEvent se)
{ {
//take the tracker of the list of trackers (if its still there) //take the tracker of the list of trackers (if its still there)
//and ensure that we take ourselves out of the session so we are not saved
_rateTrackers.remove(_id); _rateTrackers.remove(_id);
se.getSession().removeAttribute(__TRACKER);
if (LOG.isDebugEnabled())
LOG.debug("Value removed: {}", getId());
} }
public void sessionDidActivate(HttpSessionEvent se) public void sessionDidActivate(HttpSessionEvent se)
{ {
LOG.warn("Unexpected session activation"); RateTracker tracker = (RateTracker)se.getSession().getAttribute(__TRACKER);
if (tracker!=null)
_rateTrackers.put(tracker.getId(),tracker);
} }
@Override @Override

View File

@ -19,17 +19,21 @@
package org.eclipse.jetty.util; package org.eclipse.jetty.util;
import java.io.File; import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.nio.Buffer; import java.nio.Buffer;
import java.nio.BufferOverflowException; import java.nio.BufferOverflowException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode; import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.StandardOpenOption;
import java.util.Arrays; import java.util.Arrays;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
@ -903,12 +907,48 @@ public class BufferUtil
public static ByteBuffer toMappedBuffer(File file) throws IOException public static ByteBuffer toMappedBuffer(File file) throws IOException
{ {
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) try (FileChannel channel = FileChannel.open(file.toPath(),StandardOpenOption.READ))
{ {
return raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()); return channel.map(MapMode.READ_ONLY, 0, file.length());
} }
} }
static final Field fdMappedByteBuffer;
static
{
Field fd = null;
try
{
fd=MappedByteBuffer.class.getDeclaredField("fd");
fd.setAccessible(true);
}
catch(Exception e)
{
}
fdMappedByteBuffer=fd;
}
public static boolean isMappedBuffer(ByteBuffer buffer)
{
if (!(buffer instanceof MappedByteBuffer))
return false;
MappedByteBuffer mapped = (MappedByteBuffer) buffer;
if (fdMappedByteBuffer!=null)
{
try
{
if (fdMappedByteBuffer.get(mapped) instanceof FileDescriptor)
return true;
}
catch(Exception e)
{
}
}
return false;
}
public static ByteBuffer toBuffer(Resource resource,boolean direct) throws IOException public static ByteBuffer toBuffer(Resource resource,boolean direct) throws IOException
{ {
int len=(int)resource.length(); int len=(int)resource.length();
@ -1156,4 +1196,5 @@ public class BufferUtil
} }

View File

@ -21,13 +21,20 @@ package org.eclipse.jetty.util;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.nio.BufferOverflowException; import java.nio.BufferOverflowException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.StandardCharsets;
import java.nio.file.OpenOption;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@ -335,4 +342,39 @@ public class BufferUtilTest
BufferUtil.writeTo(buffer.asReadOnlyBuffer(), out); BufferUtil.writeTo(buffer.asReadOnlyBuffer(), out);
assertThat("Bytes in out equal bytes in buffer", Arrays.equals(bytes, out.toByteArray()), is(true)); assertThat("Bytes in out equal bytes in buffer", Arrays.equals(bytes, out.toByteArray()), is(true));
} }
@Test
public void testMappedFile() throws Exception
{
String data="Now is the time for all good men to come to the aid of the party";
File file = File.createTempFile("test",".txt");
file.deleteOnExit();
try(FileWriter out = new FileWriter(file);)
{
out.write(data);
}
ByteBuffer mapped = BufferUtil.toMappedBuffer(file);
assertEquals(data,BufferUtil.toString(mapped));
assertTrue(BufferUtil.isMappedBuffer(mapped));
ByteBuffer direct = BufferUtil.allocateDirect(data.length());
direct.clear();
direct.put(data.getBytes(StandardCharsets.ISO_8859_1));
direct.flip();
assertEquals(data,BufferUtil.toString(direct));
assertFalse(BufferUtil.isMappedBuffer(direct));
ByteBuffer slice = direct.slice();
assertEquals(data,BufferUtil.toString(slice));
assertFalse(BufferUtil.isMappedBuffer(slice));
ByteBuffer duplicate = direct.duplicate();
assertEquals(data,BufferUtil.toString(duplicate));
assertFalse(BufferUtil.isMappedBuffer(duplicate));
ByteBuffer readonly = direct.asReadOnlyBuffer();
assertEquals(data,BufferUtil.toString(readonly));
assertFalse(BufferUtil.isMappedBuffer(readonly));
}
} }