diff --git a/bin/jmeter.properties b/bin/jmeter.properties index 8acd6904a92..ed542450dcd 100644 --- a/bin/jmeter.properties +++ b/bin/jmeter.properties @@ -997,6 +997,9 @@ beanshell.server.file=../extras/startup.bsh # ORO PatternCacheLRU size #oro.patterncache.size=1000 +# CSS parser LRU cache size +#css.parser.cache.size=400 + #TestBeanGui # #propertyEditorSearchPath=null diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/parser/CssParser.java b/src/protocol/http/org/apache/jmeter/protocol/http/parser/CssParser.java index 68c8b8c070f..bad4c93351e 100644 --- a/src/protocol/http/org/apache/jmeter/protocol/http/parser/CssParser.java +++ b/src/protocol/http/org/apache/jmeter/protocol/http/parser/CssParser.java @@ -21,9 +21,13 @@ import java.net.URL; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.collections.map.LRUMap; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.logging.LoggingManager; @@ -50,7 +54,16 @@ public class CssParser implements LinkExtractorParser { private static final boolean IGNORE_UNRECOVERABLE_PARSING_ERROR = JMeterUtils.getPropDefault("httpsampler.ignore_failed_embedded_resource", false); //$NON-NLS-1$ private static final Logger LOG = LoggingManager.getLoggerForClass(); + + private static final int CSS_URL_CACHE_MAX_SIZE = JMeterUtils.getPropDefault("css.parser.cache.size", 0); + private static Map CSS_URL_CACHE = null; + static { + if(CSS_URL_CACHE_MAX_SIZE > 0) { + CSS_URL_CACHE = Collections.synchronizedMap(new LRUMap(CSS_URL_CACHE_MAX_SIZE)); + } + } + private static final class CustomLoggingCSSParseExceptionCallback extends LoggingCSSParseExceptionCallback { /** * @@ -76,6 +89,7 @@ public void onException(ParseException ex) { } } } + /** * */ @@ -93,40 +107,55 @@ public CssParser() { public Iterator getEmbeddedResourceURLs(String userAgent, byte[] data, final URL baseUrl, String encoding) throws LinkExtractorParseException { try { - String cssContent = new String(data, encoding); - final CascadingStyleSheet aCSS = CSSReader.readFromStringStream(cssContent, - new CSSReaderSettings() - .setBrowserCompliantMode(true) - .setFallbackCharset(Charset.forName(encoding)) - .setCSSVersion (ECSSVersion.CSS30) - .setCustomErrorHandler(new LoggingCSSParseErrorHandler()) - .setCustomExceptionHandler (new CustomLoggingCSSParseExceptionCallback(baseUrl))); - final List list = new ArrayList<>(); - final URLCollection urlCollection = new URLCollection(list); - if(aCSS != null) { - CSSVisitor.visitCSSUrl(aCSS, new DefaultCSSUrlVisitor() { - @Override - public void onImport(final CSSImportRule importRule) { - String location = importRule.getLocationString(); - if(!StringUtils.isEmpty(location)) { - urlCollection.addURL(location, baseUrl); + boolean cacheEnabled = CSS_URL_CACHE_MAX_SIZE > 0; + String md5Key = null; + URLCollection urlCollection = null; + if(cacheEnabled) { + md5Key = DigestUtils.md5Hex(data); + urlCollection = CSS_URL_CACHE.get(md5Key); + } + + if(urlCollection == null) { + String cssContent = new String(data, encoding); + final CascadingStyleSheet aCSS = CSSReader.readFromStringStream(cssContent, + new CSSReaderSettings() + .setBrowserCompliantMode(true) + .setFallbackCharset(Charset.forName(encoding)) + .setCSSVersion (ECSSVersion.CSS30) + .setCustomErrorHandler(new LoggingCSSParseErrorHandler()) + .setCustomExceptionHandler (new CustomLoggingCSSParseExceptionCallback(baseUrl))); + final List list = new ArrayList<>(); + urlCollection = new URLCollection(list); + final URLCollection localCollection = urlCollection; + if(aCSS != null) { + CSSVisitor.visitCSSUrl(aCSS, new DefaultCSSUrlVisitor() { + @Override + public void onImport(final CSSImportRule importRule) { + String location = importRule.getLocationString(); + if(!StringUtils.isEmpty(location)) { + localCollection.addURL(location, baseUrl); + } } + // Call for URLs outside of URLs + @Override + public void onUrlDeclaration( + final ICSSTopLevelRule aTopLevelRule, + final CSSDeclaration aDeclaration, + final CSSExpressionMemberTermURI aURITerm) { + // NOOP + // Browser fetch such urls only when CSS rule matches + // so we disable this code + //urlCollection.addURL(aURITerm.getURIString(), baseUrl); + } + }); + if(cacheEnabled) { + CSS_URL_CACHE.put(md5Key, urlCollection); } - // Call for URLs outside of URLs - @Override - public void onUrlDeclaration( - final ICSSTopLevelRule aTopLevelRule, - final CSSDeclaration aDeclaration, - final CSSExpressionMemberTermURI aURITerm) { - // NOOP - // Browser fetch such urls only when CSS rule matches - // so we disable this code - //urlCollection.addURL(aURITerm.getURIString(), baseUrl); - } - }); - } else { - LOG.warn("Failed parsing url:"+baseUrl+", got null CascadingStyleSheet"); + } else { + LOG.warn("Failed parsing url:"+baseUrl+", got null CascadingStyleSheet"); + } } + if(LOG.isDebugEnabled()) { StringBuilder builder = new StringBuilder(); for (Iterator iterator = urlCollection.iterator(); iterator.hasNext();) { @@ -135,6 +164,7 @@ public void onUrlDeclaration( } LOG.debug("Parsed:"+baseUrl+", got:"+builder.toString()); } + return urlCollection.iterator(); } catch (Exception e) { throw new LinkExtractorParseException(e);