Merge pull request #3363 from isopov/comnpression-excluded-useragents

* pr/3363:
  Add HTTP compression excludeUserAgents property
This commit is contained in:
Phillip Webb 2015-07-10 16:32:01 -07:00
commit fd0b1c6332
7 changed files with 85 additions and 25 deletions

View File

@ -68,6 +68,7 @@ content into your application; rather pick only the properties that you need.
server.port=8080
server.address= # bind to a specific NIC
server.compression.enabled=false # if response compression is enabled
server.compression.exclude-user-agents= # list of user-agents to exclude from compression
server.compression.mime-types=text/html,text/xml,text/plain,text/css # comma-separated list of MIME types that should be compressed
server.compression.min-response-size=2048 # minimum response size that is required for compression to be performed
server.context-parameters.*= # Servlet context init parameters, e.g. server.context-parameters.a=alpha

View File

@ -37,7 +37,12 @@ public class Compression {
"text/css" };
/**
* Minimum response size that is required for compression to be performed
* Comma-separated list of user agents for which responses should not be compressed.
*/
private String[] excludedUserAgents = null;
/**
* Minimum response size that is required for compression to be performed.
*/
private int minResponseSize = 2048;
@ -65,4 +70,11 @@ public class Compression {
this.minResponseSize = minSize;
}
public String[] getExcludedUserAgents() {
return this.excludedUserAgents;
}
public void setExcludedUserAgents(String[] excludedUserAgents) {
this.excludedUserAgents = excludedUserAgents;
}
}

View File

@ -588,6 +588,12 @@ public class JettyEmbeddedServletContainerFactory extends
.invoke(handler,
new HashSet<String>(Arrays.asList(compression
.getMimeTypes())));
if (compression.getExcludedUserAgents() != null) {
ReflectionUtils.findMethod(handlerClass, "setExcluded", Set.class)
.invoke(handler,
new HashSet<String>(Arrays.asList(compression
.getExcludedUserAgents())));
}
return handler;
}
catch (Exception ex) {
@ -605,6 +611,10 @@ public class JettyEmbeddedServletContainerFactory extends
gzipHandler.setMinGzipSize(compression.getMinResponseSize());
gzipHandler.setMimeTypes(new HashSet<String>(Arrays.asList(compression
.getMimeTypes())));
if (compression.getExcludedUserAgents() != null) {
gzipHandler.setExcluded(new HashSet<String>(Arrays.asList(compression
.getExcludedUserAgents())));
}
return gzipHandler;
}
@ -623,6 +633,11 @@ public class JettyEmbeddedServletContainerFactory extends
ReflectionUtils.findMethod(handlerClass, "setIncludedMimeTypes",
String[].class).invoke(handler,
new Object[] { compression.getMimeTypes() });
if (compression.getExcludedUserAgents() != null) {
ReflectionUtils.findMethod(handlerClass, "setExcludedAgentPatterns",
String[].class).invoke(handler,
new Object[] { compression.getExcludedUserAgents() });
}
return handler;
}
catch (Exception ex) {

View File

@ -280,6 +280,11 @@ public class TomcatEmbeddedServletContainerFactory extends
protocol.setCompressionMinSize(compression.getMinResponseSize());
protocol.setCompressableMimeTypes(StringUtils
.arrayToCommaDelimitedString(compression.getMimeTypes()));
if (getCompression().getExcludedUserAgents() != null) {
protocol.setNoCompressionUserAgents(StringUtils
.arrayToCommaDelimitedString(getCompression()
.getExcludedUserAgents()));
}
}
}

View File

@ -19,6 +19,7 @@ package org.springframework.boot.context.embedded.undertow;
import io.undertow.Handlers;
import io.undertow.Undertow;
import io.undertow.Undertow.Builder;
import io.undertow.attribute.RequestHeaderAttribute;
import io.undertow.predicate.Predicate;
import io.undertow.predicate.Predicates;
import io.undertow.server.HttpHandler;
@ -27,6 +28,7 @@ import io.undertow.server.handlers.encoding.ContentEncodingRepository;
import io.undertow.server.handlers.encoding.EncodingHandler;
import io.undertow.server.handlers.encoding.GzipEncodingProvider;
import io.undertow.servlet.api.DeploymentManager;
import io.undertow.util.HttpString;
import java.lang.reflect.Field;
import java.net.ServerSocket;
@ -100,8 +102,8 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
private Undertow createUndertowServer() {
try {
HttpHandler servletHandler = this.manager.start();
this.builder.setHandler(getContextHandler(servletHandler));
HttpHandler httpHandler = this.manager.start();
this.builder.setHandler(getContextHandler(httpHandler));
return this.builder.build();
}
catch (ServletException ex) {
@ -110,25 +112,36 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
}
}
private HttpHandler getContextHandler(HttpHandler servletHandler) {
HttpHandler contextHandler = configurationCompressionIfNecessary(servletHandler);
private HttpHandler getContextHandler(HttpHandler httpHandler) {
HttpHandler contextHandler = configurationCompressionIfNecessary(httpHandler);
if (StringUtils.isEmpty(this.contextPath)) {
return contextHandler;
}
return Handlers.path().addPrefixPath(this.contextPath, contextHandler);
}
private HttpHandler configurationCompressionIfNecessary(HttpHandler servletHandler) {
private HttpHandler configurationCompressionIfNecessary(HttpHandler httpHandler) {
if (this.compression == null || !this.compression.getEnabled()) {
return servletHandler;
return httpHandler;
}
ContentEncodingRepository encodingRepository = new ContentEncodingRepository();
Predicate mimeAndSizePredicate = Predicates.and(Predicates
.maxContentSize(this.compression.getMinResponseSize()), Predicates
.or(new CompressibleMimeTypePredicate(this.compression.getMimeTypes())));
encodingRepository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50,
mimeAndSizePredicate);
return new EncodingHandler(encodingRepository).setNext(servletHandler);
ContentEncodingRepository repository = new ContentEncodingRepository();
repository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50,
Predicates.and(getCompressionPredicates(this.compression)));
return new EncodingHandler(repository).setNext(httpHandler);
}
private Predicate[] getCompressionPredicates(Compression compression) {
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(Predicates.maxContentSize(compression.getMinResponseSize()));
predicates.add(new CompressibleMimeTypePredicate(compression.getMimeTypes()));
if (compression.getExcludedUserAgents() != null) {
for (String agent : compression.getExcludedUserAgents()) {
RequestHeaderAttribute agentHeader = new RequestHeaderAttribute(
new HttpString(HttpHeaders.USER_AGENT));
predicates.add(Predicates.not(Predicates.regex(agentHeader, agent)));
}
}
return predicates.toArray(new Predicate[predicates.size()]);
}
private String getPortsDescription() {

View File

@ -531,35 +531,43 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
@Test
public void compression() throws Exception {
assertTrue(doTestCompression(10000, null));
assertTrue(doTestCompression(10000, null, null));
}
@Test
public void noCompressionForSmallResponse() throws Exception {
assertFalse(doTestCompression(100, null));
assertFalse(doTestCompression(100, null, null));
}
@Test
public void noCompressionForMimeType() throws Exception {
String[] mimeTypes = new String[] { "text/html", "text/xml", "text/css" };
assertFalse(doTestCompression(10000, mimeTypes));
assertFalse(doTestCompression(10000, mimeTypes, null));
}
private boolean doTestCompression(int contentSize, String[] mimeTypes)
throws Exception {
String testContent = setUpFactoryForCompression(contentSize, mimeTypes);
@Test
public void noCompressionForUserAgent() throws Exception {
assertFalse(doTestCompression(10000, null, new String[] { "testUserAgent" }));
}
private boolean doTestCompression(int contentSize, String[] mimeTypes,
String[] excludedUserAgents) throws Exception {
String testContent = setUpFactoryForCompression(contentSize, mimeTypes,
excludedUserAgents);
TestGzipInputStreamFactory inputStreamFactory = new TestGzipInputStreamFactory();
Map<String, InputStreamFactory> contentDecoderMap = singletonMap("gzip",
(InputStreamFactory) inputStreamFactory);
String response = getResponse(getLocalUrl("/test.txt"),
String response = getResponse(
getLocalUrl("/test.txt"),
new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create()
.setUserAgent("testUserAgent")
.setContentDecoderRegistry(contentDecoderMap).build()));
assertThat(response, equalTo(testContent));
return inputStreamFactory.wasCompressionUsed();
}
protected String setUpFactoryForCompression(int contentSize, String[] mimeTypes)
throws Exception {
protected String setUpFactoryForCompression(int contentSize, String[] mimeTypes,
String[] excludedUserAgents) throws Exception {
char[] chars = new char[contentSize];
Arrays.fill(chars, 'F');
String testContent = new String(chars);
@ -572,6 +580,9 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
if (mimeTypes != null) {
compression.setMimeTypes(mimeTypes);
}
if (excludedUserAgents != null) {
compression.setExcludedUserAgents(excludedUserAgents);
}
factory.setCompression(compression);
this.container = factory.getEmbeddedServletContainer();
this.container.start();

View File

@ -186,8 +186,8 @@ public class JettyEmbeddedServletContainerFactoryTests extends
@Override
@SuppressWarnings("serial")
// Workaround for Jetty issue - https://bugs.eclipse.org/bugs/show_bug.cgi?id=470646
protected String setUpFactoryForCompression(final int contentSize, String[] mimeTypes)
throws Exception {
protected String setUpFactoryForCompression(final int contentSize,
String[] mimeTypes, String[] excludedUserAgents) throws Exception {
char[] chars = new char[contentSize];
Arrays.fill(chars, 'F');
final String testContent = new String(chars);
@ -197,6 +197,9 @@ public class JettyEmbeddedServletContainerFactoryTests extends
if (mimeTypes != null) {
compression.setMimeTypes(mimeTypes);
}
if (excludedUserAgents != null) {
compression.setExcludedUserAgents(excludedUserAgents);
}
factory.setCompression(compression);
this.container = factory.getEmbeddedServletContainer(new ServletRegistrationBean(
new HttpServlet() {