Merge branch 'gh-3296'

pull/3351/head
Andy Wilkinson 10 years ago
commit 7ecc271617

@ -200,11 +200,6 @@
<artifactId>HikariCP-java6</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>

@ -1,53 +0,0 @@
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web;
import org.eclipse.jetty.servlets.GzipFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link GzipFilter}.
*
* @author Andy Wilkinson
* @since 1.2.2
*/
@Configuration
@ConditionalOnClass(GzipFilter.class)
@EnableConfigurationProperties(GzipFilterProperties.class)
public class GzipFilterAutoConfiguration {
@Autowired
private GzipFilterProperties properties;
@Bean
@ConditionalOnProperty(prefix = "spring.http.gzip", name = "enabled", matchIfMissing = true)
public FilterRegistrationBean gzipFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean(new GzipFilter());
registration.addUrlPatterns("/*");
registration.setInitParameters(this.properties.getAsInitParameters());
return registration;
}
}

@ -1,242 +0,0 @@
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jetty.servlets.GzipFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpMethod;
import org.springframework.util.MimeType;
import org.springframework.util.StringUtils;
/**
* Properties for configuring {@link GzipFilter}.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @since 1.2.2
*/
@ConfigurationProperties(prefix = "spring.http.gzip")
public class GzipFilterProperties {
private final Map<String, String> initParameters = new HashMap<String, String>();
/**
* Size of the output buffer in bytes.
*/
private Integer bufferSize;
/**
* Minimum content length required for compression to occur.
*/
private Integer minGzipSize;
/**
* Level used for deflate compression (0-9).
*/
private Integer deflateCompressionLevel;
/**
* noWrap setting for deflate compression.
*/
private Boolean deflateNoWrap;
/**
* Comma-separated list of HTTP methods for which compression is enabled.
*/
private List<HttpMethod> methods;
/**
* Comma-separated list of MIME types which should be compressed.
*/
private List<MimeType> mimeTypes;
/**
* Comma-separated list of MIME types to exclude from compression.
*/
private List<MimeType> excludedMimeTypes;
/**
* Comma-separated list of user agents to exclude from compression. String.contains is
* used to determine a match against the request's User-Agent header.
*/
private String excludedAgents;
/**
* Comma-separated list of regular expression patterns to control user agents excluded
* from compression.
*/
private String excludeAgentPatterns;
/**
* Comma-separated list of paths to exclude from compression. Uses String.startsWith
* to determine a match against the request's path.
*/
private String excludePaths;
/**
* Comma-separated list of regular expression patterns to control the paths that are
* excluded from compression.
*/
private String excludePathPatterns;
/**
* Vary header sent on responses that may be compressed.
*/
private String vary;
public GzipFilterProperties() {
this.addInitParameter("checkGzExists", false);
}
public Integer getBufferSize() {
return this.bufferSize;
}
public void setBufferSize(Integer bufferSize) {
this.addInitParameter("bufferSize", bufferSize);
this.bufferSize = bufferSize;
}
public Integer getMinGzipSize() {
return this.minGzipSize;
}
public void setMinGzipSize(Integer minGzipSize) {
this.addInitParameter("minGzipSize", minGzipSize);
this.minGzipSize = minGzipSize;
}
public Integer getDeflateCompressionLevel() {
return this.deflateCompressionLevel;
}
public void setDeflateCompressionLevel(Integer deflateCompressionLevel) {
this.addInitParameter("deflateCompressionLevel", deflateCompressionLevel);
this.deflateCompressionLevel = deflateCompressionLevel;
}
public Boolean getDeflateNoWrap() {
return this.deflateNoWrap;
}
public void setDeflateNoWrap(Boolean deflateNoWrap) {
this.addInitParameter("deflateNoWrap", deflateNoWrap);
this.deflateNoWrap = deflateNoWrap;
}
public List<HttpMethod> getMethods() {
return this.methods;
}
public void setMethods(List<HttpMethod> methods) {
this.addInitParameter("methods",
StringUtils.collectionToCommaDelimitedString(methods));
this.methods = methods;
}
public List<MimeType> getMimeTypes() {
return this.mimeTypes;
}
public void setMimeTypes(List<MimeType> mimeTypes) {
this.addInitParameter("mimeTypes",
StringUtils.collectionToCommaDelimitedString(mimeTypes));
this.mimeTypes = mimeTypes;
}
public List<MimeType> getExcludedMimeTypes() {
return this.excludedMimeTypes;
}
public void setExcludedMimeTypes(List<MimeType> excludedMimeTypes) {
this.addInitParameter("excludedMimeTypes",
StringUtils.collectionToCommaDelimitedString(excludedMimeTypes));
this.excludedMimeTypes = excludedMimeTypes;
}
public String getExcludedAgents() {
return this.excludedAgents;
}
public void setExcludedAgents(String excludedAgents) {
this.addInitParameter("excludedAgents", excludedAgents);
this.excludedAgents = excludedAgents;
}
public String getExcludeAgentPatterns() {
return this.excludeAgentPatterns;
}
public void setExcludeAgentPatterns(String excludeAgentPatterns) {
this.addInitParameter("excludeAgentPatterns", excludeAgentPatterns);
this.excludeAgentPatterns = excludeAgentPatterns;
}
public String getExcludePaths() {
return this.excludePaths;
}
public void setExcludePaths(String excludePaths) {
this.addInitParameter("excludePaths", excludePaths);
this.excludePaths = excludePaths;
}
public String getExcludePathPatterns() {
return this.excludePathPatterns;
}
public void setExcludePathPatterns(String excludePathPatterns) {
this.addInitParameter("excludePathPatterns", excludePathPatterns);
this.excludePathPatterns = excludePathPatterns;
}
public String getVary() {
return this.vary;
}
public void setVary(String vary) {
this.addInitParameter("vary", vary);
this.vary = vary;
}
Map<String, String> getAsInitParameters() {
return this.initParameters;
}
private void addInitParameter(String name, Integer value) {
if (value != null) {
this.initParameters.put(name, value.toString());
}
}
private void addInitParameter(String name, Boolean value) {
if (value != null) {
this.initParameters.put(name, value.toString());
}
}
private void addInitParameter(String name, String value) {
if (value != null) {
this.initParameters.put(name, value.toString());
}
}
}

@ -31,6 +31,7 @@ import org.apache.catalina.valves.RemoteIpValve;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.springframework.boot.context.embedded.Compression;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor;
@ -99,6 +100,9 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
private final Undertow undertow = new Undertow();
@NestedConfigurationProperty
private Compression compression = new Compression();
@NestedConfigurationProperty
private JspServlet jspServlet;
@ -120,6 +124,10 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
return this.undertow;
}
public Compression getCompression() {
return this.compression;
}
public String getContextPath() {
return this.contextPath;
}
@ -239,6 +247,9 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
if (getJspServlet() != null) {
container.setJspServlet(getJspServlet());
}
if (getCompression() != null) {
container.setCompression(getCompression());
}
if (container instanceof TomcatEmbeddedServletContainerFactory) {
getTomcat()
.customizeTomcat((TomcatEmbeddedServletContainerFactory) container);
@ -347,19 +358,6 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
*/
private String uriEncoding;
/**
* Controls response compression. Acceptable values are "off" to disable
* compression, "on" to enable compression of responses over 2048 bytes, "force"
* to force response compression, or an integer value to enable compression of
* responses with content length that is at least that value.
*/
private String compression = "off";
/**
* Comma-separated list of MIME types for which compression is used.
*/
private String compressableMimeTypes = "text/html,text/xml,text/plain";
public int getMaxThreads() {
return this.maxThreads;
}
@ -408,22 +406,6 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
this.accessLogPattern = accessLogPattern;
}
public String getCompressableMimeTypes() {
return this.compressableMimeTypes;
}
public void setCompressableMimeTypes(String compressableMimeTypes) {
this.compressableMimeTypes = compressableMimeTypes;
}
public String getCompression() {
return this.compression;
}
public void setCompression(String compression) {
this.compression = compression;
}
public String getInternalProxies() {
return this.internalProxies;
}
@ -484,7 +466,6 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
if (this.maxHttpHeaderSize > 0) {
customizeMaxHttpHeaderSize(factory);
}
customizeCompression(factory);
if (this.accessLogEnabled) {
customizeAccessLog(factory);
}
@ -553,33 +534,6 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
});
}
private void customizeCompression(TomcatEmbeddedServletContainerFactory factory) {
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractHttp11Protocol) {
@SuppressWarnings("rawtypes")
AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler;
protocol.setCompression(coerceCompression(Tomcat.this.compression));
protocol.setCompressableMimeTypes(Tomcat.this.compressableMimeTypes);
}
}
private String coerceCompression(String compression) {
if ("true".equalsIgnoreCase(compression)) {
return "on";
}
if ("false".equalsIgnoreCase(compression)) {
return "off";
}
return compression;
}
});
}
private void customizeAccessLog(TomcatEmbeddedServletContainerFactory factory) {
AccessLogValve valve = new AccessLogValve();
String accessLogPattern = getAccessLogPattern();
@ -587,7 +541,6 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
valve.setSuffix(".log");
factory.addContextValves(valve);
}
}
public static class Undertow {

@ -68,7 +68,6 @@ org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.GzipFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\

@ -1,123 +0,0 @@
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link GzipFilterAutoConfiguration}
*
* @author Andy Wilkinson
*/
public class GzipFilterAutoConfigurationTests {
private AnnotationConfigApplicationContext context;
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void filterIsMappedToSlashStar() {
createAndRefreshContext();
FilterRegistrationBean registrationBean = this.context.getBean("gzipFilter",
FilterRegistrationBean.class);
assertThat(registrationBean.getUrlPatterns(), contains("/*"));
}
@Test
public void byDefaultCheckGzExistsIsTheOnlyInitParameter() {
createAndRefreshContext();
FilterRegistrationBean registrationBean = this.context.getBean("gzipFilter",
FilterRegistrationBean.class);
assertThat(registrationBean.getInitParameters().size(), equalTo(1));
assertThat(registrationBean.getInitParameters().get("checkGzExists"),
equalTo("false"));
}
@Test
public void customInitParameterConfiguration() {
createAndRefreshContext("spring.http.gzip.bufferSize:1234",
"spring.http.gzip.minGzipSize:2345",
"spring.http.gzip.deflateCompressionLevel:5",
"spring.http.gzip.deflateNoWrap:false",
"spring.http.gzip.methods:GET,POST",
"spring.http.gzip.mimeTypes:application/foo,application/bar",
"spring.http.gzip.excludedMimeTypes:application/biz",
"spring.http.gzip.excludedAgents:excluded-agent-1,excluded-agent-2",
"spring.http.gzip.excludeAgentPatterns:agent-pattern-1,agent-pattern-2",
"spring.http.gzip.excludePaths:/static/",
"spring.http.gzip.excludePathPatterns:path-pattern",
"spring.http.gzip.vary:vary-header-value");
FilterRegistrationBean registrationBean = this.context.getBean("gzipFilter",
FilterRegistrationBean.class);
assertThat(registrationBean.getInitParameters().size(), equalTo(13));
assertThat(registrationBean.getInitParameters().get("checkGzExists"),
equalTo("false"));
assertThat(registrationBean.getInitParameters().get("bufferSize"),
equalTo("1234"));
assertThat(registrationBean.getInitParameters().get("minGzipSize"),
equalTo("2345"));
assertThat(registrationBean.getInitParameters().get("deflateCompressionLevel"),
equalTo("5"));
assertThat(registrationBean.getInitParameters().get("deflateNoWrap"),
equalTo("false"));
assertThat(registrationBean.getInitParameters().get("methods"),
equalTo("GET,POST"));
assertThat(registrationBean.getInitParameters().get("mimeTypes"),
equalTo("application/foo,application/bar"));
assertThat(registrationBean.getInitParameters().get("excludedMimeTypes"),
equalTo("application/biz"));
assertThat(registrationBean.getInitParameters().get("excludedAgents"),
equalTo("excluded-agent-1,excluded-agent-2"));
assertThat(registrationBean.getInitParameters().get("excludeAgentPatterns"),
equalTo("agent-pattern-1,agent-pattern-2"));
assertThat(registrationBean.getInitParameters().get("excludePaths"),
equalTo("/static/"));
assertThat(registrationBean.getInitParameters().get("excludePathPatterns"),
equalTo("path-pattern"));
assertThat(registrationBean.getInitParameters().get("vary"),
equalTo("vary-header-value"));
}
@Test
public void filterCanBeDisabled() {
createAndRefreshContext("spring.http.gzip.enabled:false");
assertThat(this.context.getBeanNamesForType(FilterRegistrationBean.class).length,
is(equalTo(0)));
}
private void createAndRefreshContext(String... pairs) {
this.context = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, pairs);
this.context.register(GzipFilterAutoConfiguration.class);
this.context.refresh();
}
}

@ -23,16 +23,12 @@ import java.util.Map;
import org.apache.catalina.Valve;
import org.apache.catalina.valves.RemoteIpValve;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.junit.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -230,72 +226,9 @@ public class ServerPropertiesTests {
assertEquals("192.168.0.1", remoteIpValve.getInternalProxies());
}
@Test
public void customTomcatCompression() throws Exception {
assertThat("on", is(equalTo(configureCompression("on"))));
}
@Test
public void disableTomcatCompressionWithYaml() throws Exception {
// YAML interprets "off" as false, check that it's mapped back to off
assertThat("off", is(equalTo(configureCompression("faLSe"))));
}
@Test
public void enableTomcatCompressionWithYaml() throws Exception {
// YAML interprets "on" as true, check that it's mapped back to on
assertThat("on", is(equalTo(configureCompression("trUE"))));
}
@Test
public void customTomcatCompressableMimeTypes() throws Exception {
Map<String, String> map = new HashMap<String, String>();
map.put("server.port", "0");
map.put("server.tomcat.compressableMimeTypes", "application/foo");
bindProperties(map);
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
this.properties.customize(factory);
TomcatEmbeddedServletContainer container = (TomcatEmbeddedServletContainer) factory
.getEmbeddedServletContainer();
try {
AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) container
.getTomcat().getConnector().getProtocolHandler();
assertEquals("application/foo", protocol.getCompressableMimeTypes());
}
finally {
container.stop();
}
}
private void bindProperties(Map<String, String> map) {
new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues(
map));
}
private String configureCompression(String compression) {
Map<String, String> map = new HashMap<String, String>();
map.put("server.port", "0");
// YAML interprets "on" as true
map.put("server.tomcat.compression", compression);
bindProperties(map);
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
this.properties.customize(factory);
TomcatEmbeddedServletContainer container = (TomcatEmbeddedServletContainer) factory
.getEmbeddedServletContainer();
try {
AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) container
.getTomcat().getConnector().getProtocolHandler();
return protocol.getCompression();
}
finally {
container.stop();
}
}
}

@ -66,6 +66,9 @@ content into your application; rather pick only the properties that you need.
server.port=8080
server.address= # bind to a specific NIC
server.session-timeout= # session timeout in seconds
server.compression.enabled=false # if response compression is enabled
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
server.context-path= # the context path, defaults to '/'
server.jsp-servlet.class-name=org.apache.jasper.servlet.JspServlet # The class name of the JSP servlet
@ -89,8 +92,6 @@ content into your application; rather pick only the properties that you need.
server.ssl.trust-store-type=
server.tomcat.access-log-pattern= # log pattern of the access log
server.tomcat.access-log-enabled=false # is access logging enabled
server.tomcat.compression=off # is compression enabled (off, on, or an integer content length limit)
server.tomcat.compressable-mime-types=text/html,text/xml,text/plain # comma-separated list of mime types that Tomcat will compress
server.tomcat.internal-proxies=10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\\
192\\.168\\.\\d{1,3}\\.\\d{1,3}|\\
169\\.254\\.\\d{1,3}\\.\\d{1,3}|\\
@ -156,21 +157,6 @@ content into your application; rather pick only the properties that you need.
# HTTP message conversion
spring.http.converters.preferred-json-mapper= # the preferred JSON mapper to use for HTTP message conversion. Set to "gson" to force the use of Gson when both it and Jackson are on the classpath.
# HTTP response compression ({sc-spring-boot-autoconfigure}/web/GzipFilterProperties.{sc-ext}[GzipFilterProperties])
spring.http.gzip.buffer-size= # size of the output buffer in bytes
spring.http.gzip.deflate-compression-level= # the level used for deflate compression (0-9)
spring.http.gzip.deflate-no-wrap= # noWrap setting for deflate compression (true or false)
spring.http.gzip.enabled=true # enable gzip filter support
spring.http.gzip.excluded-agents= # comma-separated list of user agents to exclude from compression
spring.http.gzip.exclude-agent-patterns= # comma-separated list of regular expression patterns to control user agents excluded from compression
spring.http.gzip.exclude-paths= # comma-separated list of paths to exclude from compression
spring.http.gzip.exclude-path-patterns= # comma-separated list of regular expression patterns to control the paths that are excluded from compression
spring.http.gzip.methods= # comma-separated list of HTTP methods for which compression is enabled
spring.http.gzip.mime-types= # comma-separated list of MIME types which should be compressed
spring.http.gzip.excluded-mime-types= # comma-separated list of MIME types to exclude from compression
spring.http.gzip.min-gzip-size= # minimum content length required for compression to occur
spring.http.gzip.vary= # Vary header to be sent on responses that may be compressed
# JACKSON ({sc-spring-boot-autoconfigure}/jackson/JacksonProperties.{sc-ext}[JacksonProperties])
spring.jackson.date-format= # Date format string (e.g. yyyy-MM-dd HH:mm:ss), or a fully-qualified date format class name (e.g. com.fasterxml.jackson.databind.util.ISO8601DateFormat)
spring.jackson.property-naming-strategy= # One of the constants on Jackson's PropertyNamingStrategy (e.g. CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES) or the fully-qualified class name of a PropertyNamingStrategy subclass

@ -834,53 +834,27 @@ not required.
[[how-to-enable-http-response-compression]]
=== Enable HTTP response compression
Spring Boot provides two mechanisms for enabling compression of HTTP compression; one
that is Tomcat-specific and another that uses a filter and works with Jetty, Tomcat,
and Undertow.
[[how-to-enable-http-response-compression-tomcat]]
==== Enable Tomcat's HTTP response compression
Tomcat provides built-in support for HTTP response compression. It is disabled by
default, but can easily be enabled via `application.properties`:
[source,properties,indent=0,subs="verbatim,quotes,attributes"]
----
server.tomcat.compression=on
----
When set to `on` Tomcat will compress responses with a length that is at least 2048
bytes. This limit can be configured by specifying an integer value rather than `on`,
e.g.:
HTTP response compression is supported by Jetty, Tomcat, and Undertow. It can be enabled
via `application.properties`:
[source,properties,indent=0,subs="verbatim,quotes,attributes"]
----
server.tomcat.compression=4096
server.compression.enabled=true
----
By default Tomcat will only compress responses with certain MIME types
(`text/html`, `text/xml`, and `text/plain`). You can customize this using the
`server.tomcat.compressableMimeTypes` property, e.g.:
By default, responses must be at least 2048 bytes in length for compression to be
performed. This can be configured using the `server.compression.min-response-size`
property.
[source,properties,indent=0,subs="verbatim,quotes,attributes"]
----
server.tomcat.compressableMimeTypes=application/json,application/xml
----
By default, responses will only be compressed if their content type is one of the
following:
- `text/html`
- `text/xml`
- `text/plain`
- `text/css`
[[how-to-enable-http-compression-gzip-filter]]
==== Enable HTTP response compression using GzipFilter
If you're using Jetty or Undertow, or you want more sophisticated control over
HTTP response compression, Spring Boot provides auto-configuration for Jetty's
`GzipFilter`. While this filter is part of Jetty, it's compatible with Tomcat
and Undertow as well. To enable the filter, simply add a dependency on
`org.eclipse.jetty:jetty-servlets` to your application.
`GzipFilter` can be configured using the `spring.http.gzip.*` properties. See
{sc-spring-boot-autoconfigure}/web/GzipFilterProperties.{sc-ext}[`GzipFilterProperties`]
for more details.
This can be configured using the `server.compression.mime-types` property.

@ -51,9 +51,10 @@ public class WarPackagingTests {
"tomcat-embed-websocket-"));
private static final Set<String> JETTY_EXPECTED_IN_WEB_INF_LIB_PROVIDED = new HashSet<String>(
Arrays.asList("spring-boot-starter-jetty-", "jetty-util-", "javax.servlet-",
"jetty-io-", "jetty-http-", "jetty-server-", "jetty-security-",
"jetty-servlet-", "jetty-webapp-", "websocket-api",
Arrays.asList("spring-boot-starter-jetty-", "jetty-continuation",
"jetty-util-", "javax.servlet-", "jetty-io-", "jetty-http-",
"jetty-server-", "jetty-security-", "jetty-servlet-",
"jetty-servlets", "jetty-webapp-", "websocket-api",
"javax.annotation-api", "jetty-plus", "javax-websocket-server-impl-",
"asm-", "javax.websocket-api-", "asm-tree-", "asm-commons-",
"websocket-common-", "jetty-annotations-",

@ -0,0 +1,2 @@
server.compression.enabled: true
server.compression.min-response-size: 1

@ -16,17 +16,26 @@
package sample.jetty;
import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import java.util.zip.GZIPInputStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;
import static org.junit.Assert.assertEquals;
@ -34,6 +43,7 @@ import static org.junit.Assert.assertEquals;
* Basic integration tests for demo application.
*
* @author Dave Syer
* @author Andy Wilkinson
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleJettyApplication.class)
@ -53,4 +63,28 @@ public class SampleJettyApplicationTests {
assertEquals("Hello World", entity.getBody());
}
@Test
public void testCompression() throws Exception {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept-Encoding", "gzip");
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<byte[]> entity = restTemplate.exchange("http://localhost:"
+ this.port, HttpMethod.GET, requestEntity, byte[].class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream(
entity.getBody()));
try {
assertEquals("Hello World",
StreamUtils.copyToString(inflater, Charset.forName("UTF-8")));
}
finally {
inflater.close();
}
}
}

@ -0,0 +1,2 @@
server.compression.enabled: true
server.compression.min-response-size: 1

@ -16,25 +16,34 @@
package sample.jetty8;
import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import java.util.zip.GZIPInputStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;
import sample.jetty8.SampleJetty8Application;
import static org.junit.Assert.assertEquals;
/**
* Basic integration tests for demo application.
*
* @author Dave Syer
* @author Andy Wilkinson
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleJetty8Application.class)
@ -54,4 +63,28 @@ public class SampleJetty8ApplicationTests {
assertEquals("Hello World", entity.getBody());
}
@Test
public void testCompression() throws Exception {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept-Encoding", "gzip");
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<byte[]> entity = restTemplate.exchange("http://localhost:"
+ this.port, HttpMethod.GET, requestEntity, byte[].class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream(
entity.getBody()));
try {
assertEquals("Hello World",
StreamUtils.copyToString(inflater, Charset.forName("UTF-8")));
}
finally {
inflater.close();
}
}
}

@ -0,0 +1,2 @@
server.compression.enabled: true
server.compression.min-response-size: 1

@ -16,16 +16,25 @@
package sample.tomcat;
import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import java.util.zip.GZIPInputStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;
import static org.junit.Assert.assertEquals;
@ -33,6 +42,7 @@ import static org.junit.Assert.assertEquals;
* Basic integration tests for demo application.
*
* @author Dave Syer
* @author Andy Wilkinson
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleTomcatApplication.class)
@ -51,4 +61,28 @@ public class SampleTomcatApplicationTests {
assertEquals("Hello World", entity.getBody());
}
@Test
public void testCompression() throws Exception {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept-Encoding", "gzip");
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<byte[]> entity = restTemplate.exchange("http://localhost:"
+ this.port, HttpMethod.GET, requestEntity, byte[].class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream(
entity.getBody()));
try {
assertEquals("Hello World",
StreamUtils.copyToString(inflater, Charset.forName("UTF-8")));
}
finally {
inflater.close();
}
}
}

@ -1,3 +1,5 @@
server.undertow.access-log-enabled=true
server.undertow.access-log-dir=target/logs
server.undertow.access-log-pattern=combined
server.undertow.access-log-pattern=combined
server.compression.enabled=true
server.compression.min-response-size=1

@ -16,17 +16,26 @@
package sample.undertow;
import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import java.util.zip.GZIPInputStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;
import static org.junit.Assert.assertEquals;
@ -56,6 +65,30 @@ public class SampleUndertowApplicationTests {
assertOkResponse("/async", "async: Hello World");
}
@Test
public void testCompression() throws Exception {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept-Encoding", "gzip");
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<byte[]> entity = restTemplate.exchange("http://localhost:"
+ this.port, HttpMethod.GET, requestEntity, byte[].class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream(
entity.getBody()));
try {
assertEquals("Hello World",
StreamUtils.copyToString(inflater, Charset.forName("UTF-8")));
}
finally {
inflater.close();
}
}
private void assertOkResponse(String path, String body) {
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + this.port + path, String.class);

@ -18,6 +18,10 @@
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>

@ -154,6 +154,11 @@
<artifactId>jetty-util</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>

@ -35,6 +35,7 @@ import org.springframework.util.ClassUtils;
* @author Dave Syer
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Ivan Sopov
* @see AbstractEmbeddedServletContainerFactory
*/
public abstract class AbstractConfigurableEmbeddedServletContainer implements
@ -67,6 +68,8 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements
private JspServlet jspServlet = new JspServlet();
private Compression compression;
/**
* Create a new {@link AbstractConfigurableEmbeddedServletContainer} instance.
*/
@ -278,6 +281,14 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements
return this.jspServlet;
}
public Compression getCompression() {
return this.compression;
}
public void setCompression(Compression compression) {
this.compression = compression;
}
/**
* Utility method that can be used by subclasses wishing to combine the specified
* {@link ServletContextInitializer} parameters with those defined in this instance.

@ -0,0 +1,68 @@
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.embedded;
/**
* Simple container-independent abstraction for compression configuration.
*
* @author Ivan Sopov
* @author Andy Wilkinson
* @since 1.3.0
*/
public class Compression {
/**
* If response compression is enabled.
*/
private boolean enabled = false;
/**
* Comma-separated list of MIME types that should be compressed.
*/
private String[] mimeTypes = new String[] { "text/html", "text/xml", "text/plain",
"text/css" };
/**
* Minimum response size that is required for compression to be performed
*/
private int minResponseSize = 2048;
public boolean getEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String[] getMimeTypes() {
return this.mimeTypes;
}
public void setMimeTypes(String[] mimeTypes) {
this.mimeTypes = mimeTypes;
}
public int getMinResponseSize() {
return this.minResponseSize;
}
public void setMinResponseSize(int minSize) {
this.minResponseSize = minSize;
}
}

@ -170,4 +170,11 @@ public interface ConfigurableEmbeddedServletContainer {
*/
void setJspServlet(JspServlet jspServlet);
/**
* Sets the compression configuration that will be applied to the container's default
* connector.
* @param compression the compression configuration
*/
void setCompression(Compression compression);
}

@ -23,7 +23,9 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
@ -37,9 +39,11 @@ import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SessionManager;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.servlets.gzip.GzipHandler;
import org.eclipse.jetty.util.resource.JarResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@ -47,6 +51,7 @@ import org.eclipse.jetty.webapp.AbstractConfiguration;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.Compression;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
@ -59,6 +64,7 @@ import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
@ -81,6 +87,12 @@ import org.springframework.util.StringUtils;
public class JettyEmbeddedServletContainerFactory extends
AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
private static final String GZIP_HANDLER_JETTY_9_2 = "org.eclipse.jetty.servlets.gzip.GzipHandler";
private static final String GZIP_HANDLER_JETTY_8 = "org.eclipse.jetty.server.handler.GzipHandler";
private static final String GZIP_HANDLER_JETTY_9_3 = "org.eclipse.jetty.server.handler.gzip.GzipHandler";
private List<Configuration> configurations = new ArrayList<Configuration>();
private List<JettyServerCustomizer> jettyServerCustomizers = new ArrayList<JettyServerCustomizer>();
@ -120,7 +132,14 @@ public class JettyEmbeddedServletContainerFactory extends
int port = (getPort() >= 0 ? getPort() : 0);
Server server = new Server(new InetSocketAddress(getAddress(), port));
configureWebAppContext(context, initializers);
server.setHandler(context);
if (getCompression() != null && getCompression().getEnabled()) {
HandlerWrapper gzipHandler = createGzipHandler();
gzipHandler.setHandler(context);
server.setHandler(gzipHandler);
}
else {
server.setHandler(context);
}
this.logger.info("Server initialized with port: " + port);
if (getSsl() != null && getSsl().isEnabled()) {
SslContextFactory sslContextFactory = new SslContextFactory();
@ -135,6 +154,21 @@ public class JettyEmbeddedServletContainerFactory extends
return getJettyEmbeddedServletContainer(server);
}
private HandlerWrapper createGzipHandler() {
if (ClassUtils.isPresent(GZIP_HANDLER_JETTY_9_2, getClass().getClassLoader())) {
return new Jetty92GzipHandlerFactory().createGzipHandler(getCompression());
}
else if (ClassUtils.isPresent(GZIP_HANDLER_JETTY_8, getClass().getClassLoader())) {
return new Jetty8GzipHandlerFactory().createGzipHandler(getCompression());
}
else if (ClassUtils
.isPresent(GZIP_HANDLER_JETTY_9_3, getClass().getClassLoader())) {
return new Jetty93GzipHandlerFactory().createGzipHandler(getCompression());
}
throw new IllegalStateException(
"Compression is enabled, but GzipHandler is not on the classpath");
}
private SslServerConnectorFactory getSslServerConnectorFactory() {
if (ClassUtils.isPresent("org.eclipse.jetty.server.ssl.SslSocketConnector", null)) {
return new Jetty8SslServerConnectorFactory();
@ -534,4 +568,73 @@ public class JettyEmbeddedServletContainerFactory extends
}
private interface GzipHandlerFactory {
HandlerWrapper createGzipHandler(Compression compression);
}
private static class Jetty8GzipHandlerFactory implements GzipHandlerFactory {
@Override
public HandlerWrapper createGzipHandler(Compression compression) {
try {
Class<?> gzipHandlerClass = ClassUtils.forName(GZIP_HANDLER_JETTY_8,
getClass().getClassLoader());
HandlerWrapper gzipHandler = (HandlerWrapper) gzipHandlerClass
.newInstance();
ReflectionUtils.findMethod(gzipHandlerClass, "setMinGzipSize", int.class)
.invoke(gzipHandler, compression.getMinResponseSize());
ReflectionUtils.findMethod(gzipHandlerClass, "setMimeTypes", Set.class)
.invoke(gzipHandler,
new HashSet<String>(Arrays.asList(compression
.getMimeTypes())));
return gzipHandler;
}
catch (Exception ex) {
throw new RuntimeException("Failed to configure Jetty 8 gzip handler", ex);
}
}
}
private static class Jetty92GzipHandlerFactory implements GzipHandlerFactory {
@Override
public HandlerWrapper createGzipHandler(Compression compression) {
GzipHandler gzipHandler = new GzipHandler();
gzipHandler.setMinGzipSize(compression.getMinResponseSize());
gzipHandler.setMimeTypes(new HashSet<String>(Arrays.asList(compression
.getMimeTypes())));
return gzipHandler;
}
}
private static class Jetty93GzipHandlerFactory implements GzipHandlerFactory {
@Override
public HandlerWrapper createGzipHandler(Compression compression) {
try {
Class<?> gzipHandlerClass = ClassUtils.forName(GZIP_HANDLER_JETTY_9_3,
getClass().getClassLoader());
HandlerWrapper gzipHandler = (HandlerWrapper) gzipHandlerClass
.newInstance();
ReflectionUtils.findMethod(gzipHandlerClass, "setMinGzipSize", int.class)
.invoke(gzipHandler, compression.getMinResponseSize());
ReflectionUtils.findMethod(gzipHandlerClass, "setIncludedMimeTypes",
String[].class).invoke(gzipHandler,
new Object[] { compression.getMimeTypes() });
return gzipHandler;
}
catch (Exception ex) {
throw new RuntimeException("Failed to configure Jetty 9.3 gzip handler",
ex);
}
}
}
}

@ -46,7 +46,9 @@ import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.Tomcat.FixContextListener;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
@ -255,6 +257,18 @@ public class TomcatEmbeddedServletContainerFactory extends
connector.setSecure(true);
}
if (getCompression() != null && getCompression().getEnabled()) {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractHttp11Protocol) {
@SuppressWarnings("rawtypes")
AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler;
protocol.setCompression("on");
protocol.setCompressionMinSize(getCompression().getMinResponseSize());
protocol.setCompressableMimeTypes(StringUtils
.arrayToCommaDelimitedString(getCompression().getMimeTypes()));
}
}
for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
customizer.customize(connector);
}

@ -19,7 +19,13 @@ package org.springframework.boot.context.embedded.undertow;
import io.undertow.Handlers;
import io.undertow.Undertow;
import io.undertow.Undertow.Builder;
import io.undertow.predicate.Predicate;
import io.undertow.predicate.Predicates;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
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 java.lang.reflect.Field;
@ -31,8 +37,12 @@ import javax.servlet.ServletException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.embedded.Compression;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
import org.springframework.http.HttpHeaders;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@ -59,16 +69,19 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
private final boolean autoStart;
private final Compression compression;
private Undertow undertow;
private boolean started = false;
public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager,
String contextPath, int port, boolean autoStart) {
String contextPath, int port, boolean autoStart, Compression compression) {
this.builder = builder;
this.manager = manager;
this.contextPath = contextPath;
this.autoStart = autoStart;
this.compression = compression;
}
@Override
@ -98,10 +111,26 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
}
private HttpHandler getContextHandler(HttpHandler servletHandler) {
HttpHandler contextHandler = configurationCompressionIfNecessary(servletHandler);
if (StringUtils.isEmpty(this.contextPath)) {
return contextHandler;
}
return Handlers.path().addPrefixPath(this.contextPath, contextHandler);
}
private HttpHandler configurationCompressionIfNecessary(HttpHandler servletHandler) {
if (this.compression == null || !this.compression.getEnabled()) {
return servletHandler;
}
return Handlers.path().addPrefixPath(this.contextPath, servletHandler);
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);
}
private String getPortsDescription() {
@ -212,4 +241,32 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
}
private static class CompressibleMimeTypePredicate implements Predicate {
private final List<MimeType> mimeTypes;
CompressibleMimeTypePredicate(String[] mimeTypes) {
this.mimeTypes = new ArrayList<MimeType>(mimeTypes.length);
for (String mimeTypeString : mimeTypes) {
this.mimeTypes.add(MimeTypeUtils.parseMimeType(mimeTypeString));
}
}
@Override
public boolean resolve(HttpServerExchange value) {
String contentType = value.getResponseHeaders().getFirst(
HttpHeaders.CONTENT_TYPE);
if (contentType != null) {
for (MimeType mimeType : this.mimeTypes) {
if (mimeType.isCompatibleWith(MimeTypeUtils
.parseMimeType(contentType))) {
return true;
}
}
}
return false;
}
}
}

@ -219,7 +219,7 @@ public class UndertowEmbeddedServletContainerFactory extends
int port = getPort();
Builder builder = createBuilder(port);
return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(),
port, port >= 0);
port, port >= 0, getCompression());
}
private Builder createBuilder(int port) {
@ -470,7 +470,7 @@ public class UndertowEmbeddedServletContainerFactory extends
protected UndertowEmbeddedServletContainer getUndertowEmbeddedServletContainer(
Builder builder, DeploymentManager manager, int port) {
return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(),
port, port >= 0);
port, port >= 0, getCompression());
}
@Override

@ -20,6 +20,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
@ -27,6 +28,8 @@ import java.security.KeyStore;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.GZIPInputStream;
import javax.net.ssl.SSLException;
import javax.servlet.GenericServlet;
@ -36,8 +39,10 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.InputStreamFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.junit.After;
@ -58,6 +63,7 @@ import org.springframework.util.SocketUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.concurrent.ListenableFuture;
import static java.util.Collections.singletonMap;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
@ -65,7 +71,9 @@ import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@ -520,6 +528,80 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
assertThat(getFactory().getSessionTimeout(), equalTo(30 * 60));
}
@Test
public void compression() throws Exception {
assertTrue(internalTestCompression(10000, null));
}
@Test
public void noCompressionForSmallResponse() throws Exception {
assertFalse(internalTestCompression(100, null));
}
@Test
public void noCompressionForMimeType() throws Exception {
assertFalse(internalTestCompression(10000, new String[] { "text/html",
"text/xml", "text/css" }));
}
protected String setUpFactoryForCompression(int contentSize, String[] mimeTypes)
throws Exception {
char[] chars = new char[contentSize];
Arrays.fill(chars, 'F');
String testContent = new String(chars);
AbstractEmbeddedServletContainerFactory factory = getFactory();
FileCopyUtils.copy(testContent,
new FileWriter(this.temporaryFolder.newFile("test.txt")));
factory.setDocumentRoot(this.temporaryFolder.getRoot());
Compression compression = new Compression();
compression.setEnabled(true);
if (mimeTypes != null) {
compression.setMimeTypes(mimeTypes);
}
factory.setCompression(compression);
this.container = factory.getEmbeddedServletContainer();
this.container.start();
return testContent;
}
private boolean internalTestCompression(int contentSize, String[] mimeTypes)
throws Exception {
String testContent = setUpFactoryForCompression(contentSize, mimeTypes);
class TestGzipInputStreamFactory implements InputStreamFactory {
final AtomicBoolean requested = new AtomicBoolean(false);
@Override
public InputStream create(InputStream instream) throws IOException {
if (this.requested.get()) {
throw new IllegalStateException(
"On deflated InputStream already requested");
}
this.requested.set(true);
return new GZIPInputStream(instream);
}
}
TestGzipInputStreamFactory gzipTestInputStreamFactory = new TestGzipInputStreamFactory();
String response = getResponse(
getLocalUrl("/test.txt"),
new HttpComponentsClientHttpRequestFactory(HttpClientBuilder
.create()
.setContentDecoderRegistry(
singletonMap("gzip",
(InputStreamFactory) gzipTestInputStreamFactory))
.build()));
assertThat(response, equalTo(testContent));
boolean wasCompressionUsed = gzipTestInputStreamFactory.requested.get();
return wasCompressionUsed;
}
private void addTestTxtFile(AbstractEmbeddedServletContainerFactory factory)
throws IOException {
FileCopyUtils.copy("test",

@ -16,11 +16,17 @@
package org.springframework.boot.context.embedded.jetty;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
@ -32,8 +38,12 @@ import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.Test;
import org.mockito.InOrder;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests;
import org.springframework.boot.context.embedded.Compression;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.Ssl;
import org.springframework.http.HttpHeaders;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
@ -173,6 +183,38 @@ public class JettyEmbeddedServletContainerFactoryTests extends
assertThat(getJspServlet().getInitParameters(), is(equalTo(initParameters)));
}
@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 {
char[] chars = new char[contentSize];
Arrays.fill(chars, 'F');
final String testContent = new String(chars);
AbstractEmbeddedServletContainerFactory factory = getFactory();
Compression compression = new Compression();
compression.setEnabled(true);
if (mimeTypes != null) {
compression.setMimeTypes(mimeTypes);
}
factory.setCompression(compression);
this.container = factory.getEmbeddedServletContainer(new ServletRegistrationBean(
new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentLength(contentSize);
resp.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain");
resp.getWriter().print(testContent);
}
}, "/test.txt"));
this.container.start();
return testContent;
}
@Override
protected ServletHolder getJspServlet() {
WebAppContext context = (WebAppContext) ((JettyEmbeddedServletContainer) this.container)

Loading…
Cancel
Save