diff --git a/core/src/main/java/io/undertow/Handlers.java b/core/src/main/java/io/undertow/Handlers.java index ef601e8499..f3de921af6 100644 --- a/core/src/main/java/io/undertow/Handlers.java +++ b/core/src/main/java/io/undertow/Handlers.java @@ -555,7 +555,7 @@ public static ResponseRateLimitingHandler responseRateLimitingHandler(HttpHandle * Creates a handler that automatically learns which resources to push based on the referer header * * @param maxEntries The maximum number of entries to store - * @param maxAge The maximum age of the entries + * @param maxAge The maximum age of the entries (ms) * @param next The next handler * @return A caching push handler */ @@ -563,6 +563,20 @@ public static LearningPushHandler learningPushHandler(int maxEntries, int maxAge return new LearningPushHandler(maxEntries, maxAge, next); } + /** + * Creates a handler that automatically learns which resources to push based on the referer header + * + * @param maxPathEntries The maximum number of entries to store. Path->{1..n Push} + * @param maxPathAge The maximum age of the entries (ms) + * @param maxPushEntries The maximum number of push entries stored for given path + * @param maxPushAge The maximum age of push entry (ms) + * @param next The next handler + * @return A caching push handler + */ + public static LearningPushHandler learningPushHandler(final int maxPathEntries, final int maxPathAge, final int maxPushEntries, final int maxPushAge, final HttpHandler next) { + return new LearningPushHandler(maxPathEntries, maxPathAge, maxPushEntries, maxPushAge, next); + } + /** * A handler that sets response code but continues the exchange so the servlet's * error page can be returned. diff --git a/core/src/main/java/io/undertow/protocols/ssl/SslConduit.java b/core/src/main/java/io/undertow/protocols/ssl/SslConduit.java index 3a2068aceb..9134c56f52 100644 --- a/core/src/main/java/io/undertow/protocols/ssl/SslConduit.java +++ b/core/src/main/java/io/undertow/protocols/ssl/SslConduit.java @@ -1001,6 +1001,7 @@ private synchronized long doWrap(ByteBuffer[] userBuffers, int off, int len) thr private SSLEngineResult wrapAndFlip(ByteBuffer[] userBuffers, int off, int len) throws IOException { SSLEngineResult result = null; + int totalConsumedBytes = 0; while (result == null || (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP && result.getStatus() != SSLEngineResult.Status.BUFFER_OVERFLOW && !engine.isInboundDone())) { if (userBuffers == null) { @@ -1008,6 +1009,10 @@ private SSLEngineResult wrapAndFlip(ByteBuffer[] userBuffers, int off, int len) } else { result = engine.wrap(userBuffers, off, len, wrappedData.getBuffer()); } + totalConsumedBytes += result.bytesConsumed(); + } + if (totalConsumedBytes != result.bytesConsumed()) { + result = new SSLEngineResult(result.getStatus(), result.getHandshakeStatus(), totalConsumedBytes, result.bytesProduced()); } wrappedData.getBuffer().flip(); return result; diff --git a/core/src/main/java/io/undertow/server/handlers/LearningPushHandler.java b/core/src/main/java/io/undertow/server/handlers/LearningPushHandler.java index d0f0f0615c..cbeeb2593a 100644 --- a/core/src/main/java/io/undertow/server/handlers/LearningPushHandler.java +++ b/core/src/main/java/io/undertow/server/handlers/LearningPushHandler.java @@ -32,7 +32,6 @@ import io.undertow.util.Headers; import io.undertow.util.Methods; -import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -47,20 +46,31 @@ public class LearningPushHandler implements HttpHandler { private static final String SESSION_ATTRIBUTE = "io.undertow.PUSHED_RESOURCES"; - private static final int DEFAULT_MAX_CACHE_ENTRIES = 1000; - private static final int DEFAULT_MAX_CACHE_AGE = -1; + private static final int DEFAULT_MAX_CACHE_ENTRIES = Integer.getInteger("io.undertow.handlers.learning-push.default-max-entries", 200); + private static final int DEFAULT_MAX_CACHE_AGE = Integer.getInteger("io.undertow.handlers.learning-push.default-max-age", LRUCache.MAX_AGE_NO_EXPIRY); - private final LRUCache> cache; + private final LRUCache> cache; private final HttpHandler next; + private int maxPushCacheEntries; + private int maxPushCacheAge; public LearningPushHandler(final HttpHandler next) { this(DEFAULT_MAX_CACHE_ENTRIES, DEFAULT_MAX_CACHE_AGE, next); } - public LearningPushHandler(int maxEntries, int maxAge, HttpHandler next) { + public LearningPushHandler(int maxPathEtries, int maxPathAge, HttpHandler next) { this.next = next; - cache = new LRUCache<>(maxEntries, maxAge); + this.maxPushCacheEntries = maxPathEtries; + this.maxPushCacheAge = maxPathAge; + cache = new LRUCache<>(maxPathEtries, maxPathAge); + } + + public LearningPushHandler(int maxPathEtries, int maxPathAge, int maxPushEtries, int maxPushAge, HttpHandler next) { + this.next = next; + this.maxPushCacheEntries = maxPushEtries; + this.maxPushCacheAge = maxPushAge; + cache = new LRUCache<>(maxPathEtries, maxPathAge); } @Override @@ -92,18 +102,21 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { private void doPush(HttpServerExchange exchange, String fullPath) { if (exchange.getConnection().isPushSupported()) { - Map toPush = cache.get(fullPath); + LRUCache toPush = cache.get(fullPath); if (toPush != null) { Session session = getSession(exchange); if (session == null) { return; } - Map pushed = (Map) session.getAttribute(SESSION_ATTRIBUTE); + LRUCache pushed = (LRUCache) session.getAttribute(SESSION_ATTRIBUTE); if (pushed == null) { - pushed = Collections.synchronizedMap(new HashMap()); + pushed = new LRUCache<>(this.maxPushCacheEntries, this.maxPushCacheAge); } - for (Map.Entry entry : toPush.entrySet()) { - PushedRequest request = entry.getValue(); + for (String entryKey : toPush.keySet()) { + PushedRequest request = toPush.get(entryKey); + if(request == null) { + continue; + } Object pushedKey = pushed.get(request.getPath()); boolean doPush = pushedKey == null; if (!doPush) { @@ -114,11 +127,12 @@ private void doPush(HttpServerExchange exchange, String fullPath) { } } if (doPush) { + //pushResource will fill request headers. exchange.getConnection().pushResource(request.getPath(), Methods.GET, request.getRequestHeaders()); if(request.getEtag() != null) { - pushed.put(request.getPath(), request.getEtag()); + pushed.add(request.getPath(), request.getEtag()); } else { - pushed.put(request.getPath(), request.getLastModified()); + pushed.add(request.getPath(), request.getLastModified()); } } } @@ -166,16 +180,16 @@ public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener lastModified = dt.getTime(); } } - Map pushes = cache.get(referer); + LRUCache pushes = cache.get(referer); if(pushes == null) { synchronized (cache) { pushes = cache.get(referer); if(pushes == null) { - cache.add(referer, pushes = Collections.synchronizedMap(new HashMap())); + cache.add(referer, pushes = new LRUCache(maxPushCacheEntries, maxPushCacheAge)); } } } - pushes.put(fullPath, new PushedRequest(new HeaderMap(), requestPath, etag, lastModified)); + pushes.add(fullPath, new PushedRequest(new HeaderMap(), requestPath, etag, lastModified)); } nextListener.proceed(); @@ -224,6 +238,8 @@ public Map> parameters() { Map> params = new HashMap<>(); params.put("max-age", Integer.class); params.put("max-entries", Integer.class); + params.put("max-push-age", Integer.class); + params.put("max-push-entries", Integer.class); return params; } @@ -241,11 +257,12 @@ public String defaultParameter() { public HandlerWrapper build(Map config) { final int maxAge = config.containsKey("max-age") ? (Integer)config.get("max-age") : DEFAULT_MAX_CACHE_AGE; final int maxEntries = config.containsKey("max-entries") ? (Integer)config.get("max-entries") : DEFAULT_MAX_CACHE_ENTRIES; - + final int maxPushAge = config.containsKey("max-push-age") ? (Integer)config.get("max-push-age") : DEFAULT_MAX_CACHE_AGE; + final int maxPushEntries = config.containsKey("max-push-entries") ? (Integer)config.get("max-push-entries") : DEFAULT_MAX_CACHE_ENTRIES; return new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { - return new LearningPushHandler(maxEntries, maxAge, handler); + return new LearningPushHandler(maxEntries, maxAge, maxPushEntries, maxPushAge, handler); } }; } diff --git a/core/src/main/java/io/undertow/server/handlers/PathHandler.java b/core/src/main/java/io/undertow/server/handlers/PathHandler.java index 429d88aa4e..304d822506 100644 --- a/core/src/main/java/io/undertow/server/handlers/PathHandler.java +++ b/core/src/main/java/io/undertow/server/handlers/PathHandler.java @@ -59,7 +59,7 @@ public PathHandler() { public PathHandler(int cacheSize) { if(cacheSize > 0) { - cache = new LRUCache<>(cacheSize, -1, true); + cache = new LRUCache<>(cacheSize, LRUCache.MAX_AGE_NO_EXPIRY, true); } else { cache = null; } diff --git a/core/src/main/java/io/undertow/server/handlers/cache/DirectBufferCache.java b/core/src/main/java/io/undertow/server/handlers/cache/DirectBufferCache.java index 520da284be..e0f59d2ffc 100644 --- a/core/src/main/java/io/undertow/server/handlers/cache/DirectBufferCache.java +++ b/core/src/main/java/io/undertow/server/handlers/cache/DirectBufferCache.java @@ -29,7 +29,6 @@ import java.util.concurrent.ConcurrentHashMap; import io.undertow.UndertowLogger; -import io.undertow.server.handlers.resource.CachingResourceManager; import io.undertow.util.ConcurrentDirectDeque; import org.xnio.BufferAllocator; @@ -49,6 +48,14 @@ */ public class DirectBufferCache { private static final int SAMPLE_INTERVAL = 5; + /** + * Max age 0, indicating that entries expire upon creation and are not retained; + */ + public static final int MAX_AGE_NO_CACHING = 0; + /** + * Mage age -1, entries dont expire + */ + public static final int MAX_AGE_NO_EXPIRY = -1; private final LimitedBufferSlicePool pool; private final ConcurrentMap cache; @@ -98,12 +105,12 @@ public CacheEntry get(Object key) { } final long expires = cacheEntry.getExpires(); - if(expires == CachingResourceManager.MAX_AGE_NO_CACHING || (expires > 0 && System.currentTimeMillis() > expires)) { + if(expires == MAX_AGE_NO_CACHING || (expires > 0 && System.currentTimeMillis() > expires)) { remove(key); return null; } - //either did not expire or CachingResourceManager.MAX_AGE_NO_EXPIRY + //either did not expire or MAX_AGE_NO_EXPIRY if (cacheEntry.hit() % SAMPLE_INTERVAL == 0) { bumpAccess(cacheEntry); @@ -235,18 +242,18 @@ public boolean enabled() { } public void enable() { - if(this.maxAge == CachingResourceManager.MAX_AGE_NO_CACHING) { - this.expires = CachingResourceManager.MAX_AGE_NO_CACHING; + if(this.maxAge == MAX_AGE_NO_CACHING) { + this.expires = MAX_AGE_NO_CACHING; disable(); - } else if(this.maxAge == CachingResourceManager.MAX_AGE_NO_EXPIRY) { - this.expires = CachingResourceManager.MAX_AGE_NO_EXPIRY; + } else if(this.maxAge == MAX_AGE_NO_EXPIRY) { + this.expires = MAX_AGE_NO_EXPIRY; this.enabled = 2; } else if(this.maxAge > 0) { this.expires = System.currentTimeMillis() + maxAge; this.enabled = 2; } else { - this.expires = CachingResourceManager.MAX_AGE_NO_CACHING; - UndertowLogger.ROOT_LOGGER.wrongCacheTTLValue(this.maxAge, CachingResourceManager.MAX_AGE_NO_CACHING); + this.expires = MAX_AGE_NO_CACHING; + UndertowLogger.ROOT_LOGGER.wrongCacheTTLValue(this.maxAge, MAX_AGE_NO_CACHING); disable(); } } diff --git a/core/src/main/java/io/undertow/server/handlers/cache/LRUCache.java b/core/src/main/java/io/undertow/server/handlers/cache/LRUCache.java index ae53a446e6..1683363526 100644 --- a/core/src/main/java/io/undertow/server/handlers/cache/LRUCache.java +++ b/core/src/main/java/io/undertow/server/handlers/cache/LRUCache.java @@ -18,6 +18,8 @@ package io.undertow.server.handlers.cache; +import java.util.Collections; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; @@ -40,6 +42,14 @@ */ public class LRUCache { private static final int SAMPLE_INTERVAL = 5; + /** + * Max age 0, indicating that entries expire upon creation and are not retained; + */ + public static final int MAX_AGE_NO_CACHING = 0; + /** + * Mage age -1, entries dont expire + */ + public static final int MAX_AGE_NO_EXPIRY = -1; /** * Max active entries that are present in the cache. @@ -61,6 +71,7 @@ public LRUCache(int maxEntries, final int maxAge) { this.maxEntries = maxEntries; this.fifo = false; } + public LRUCache(int maxEntries, final int maxAge, boolean fifo) { this.maxAge = maxAge; this.cache = new ConcurrentHashMap<>(16); @@ -73,8 +84,10 @@ public void add(K key, V newValue) { CacheEntry value = cache.get(key); if (value == null) { long expires; - if(maxAge == -1) { - expires = -1; + if(maxAge == MAX_AGE_NO_EXPIRY) { + expires = MAX_AGE_NO_EXPIRY; + } else if (maxAge == MAX_AGE_NO_CACHING) { + return; } else { expires = System.currentTimeMillis() + maxAge; } @@ -101,7 +114,7 @@ public V get(K key) { return null; } long expires = cacheEntry.getExpires(); - if(expires != -1) { + if(expires != MAX_AGE_NO_EXPIRY) { if(System.currentTimeMillis() > expires) { remove(key); return null; @@ -117,6 +130,10 @@ public V get(K key) { return cacheEntry.getValue(); } + public Set keySet(){ + return Collections.unmodifiableSet(this.cache.keySet()); + } + private void bumpAccess(CacheEntry cacheEntry) { Object prevToken = cacheEntry.claimToken(); if (!Boolean.FALSE.equals(prevToken)) { diff --git a/core/src/main/java/io/undertow/server/handlers/resource/CachingResourceManager.java b/core/src/main/java/io/undertow/server/handlers/resource/CachingResourceManager.java index 2c9f88a5da..bd45bca5ec 100644 --- a/core/src/main/java/io/undertow/server/handlers/resource/CachingResourceManager.java +++ b/core/src/main/java/io/undertow/server/handlers/resource/CachingResourceManager.java @@ -34,11 +34,11 @@ public class CachingResourceManager implements ResourceManager { /** * Max age 0, indicating that entries expire upon creation and are not retained; */ - public static final int MAX_AGE_NO_CACHING = 0; + public static final int MAX_AGE_NO_CACHING = LRUCache.MAX_AGE_NO_CACHING; /** * Mage age -1, this force manager to retain entries until underlying resource manager indicate that entries expired/changed */ - public static final int MAX_AGE_NO_EXPIRY = -1; + public static final int MAX_AGE_NO_EXPIRY = LRUCache.MAX_AGE_NO_EXPIRY; /** * The biggest file size we cache */ diff --git a/servlet/src/main/java/io/undertow/servlet/handlers/ServletPathMatches.java b/servlet/src/main/java/io/undertow/servlet/handlers/ServletPathMatches.java index 9676b601a9..13a2e03c65 100644 --- a/servlet/src/main/java/io/undertow/servlet/handlers/ServletPathMatches.java +++ b/servlet/src/main/java/io/undertow/servlet/handlers/ServletPathMatches.java @@ -79,9 +79,9 @@ public ServletPathMatches(final Deployment deployment) { this.deployment = deployment; this.welcomePages = deployment.getDeploymentInfo().getWelcomePages().toArray(new String[deployment.getDeploymentInfo().getWelcomePages().size()]); this.resourceManager = deployment.getDeploymentInfo().getResourceManager(); - this.pathMatchCacheFixed = new LRUCache<>(1000, -1, true); + this.pathMatchCacheFixed = new LRUCache<>(1000, LRUCache.MAX_AGE_NO_EXPIRY, true); this.pathMatchCacheResources = new LRUCache<>(1000, - resourceManager instanceof CachingResourceManager? ((CachingResourceManager) resourceManager).getMaxAge() : -1, true); + resourceManager instanceof CachingResourceManager ? ((CachingResourceManager) resourceManager).getMaxAge() : CachingResourceManager.MAX_AGE_NO_EXPIRY, true); // add change listener for welcome pages if (this.resourceManager.isResourceChangeListenerSupported()) { try { diff --git a/servlet/src/main/java/io/undertow/servlet/spec/ServletContextImpl.java b/servlet/src/main/java/io/undertow/servlet/spec/ServletContextImpl.java index 48580eaf59..62b4a8eaaf 100644 --- a/servlet/src/main/java/io/undertow/servlet/spec/ServletContextImpl.java +++ b/servlet/src/main/java/io/undertow/servlet/spec/ServletContextImpl.java @@ -145,7 +145,7 @@ public ServletContextImpl(final ServletContainer servletContainer, final Deploym this.attributes = deploymentInfo.getServletContextAttributeBackingMap(); } attributes.putAll(deployment.getDeploymentInfo().getServletContextAttributes()); - this.contentTypeCache = new LRUCache<>(deployment.getDeploymentInfo().getContentTypeCacheSize(), -1, true); + this.contentTypeCache = new LRUCache<>(deployment.getDeploymentInfo().getContentTypeCacheSize(), LRUCache.MAX_AGE_NO_EXPIRY, true); this.defaultSessionTimeout = deploymentInfo.getDefaultSessionTimeout() / 60; }