Add HTTP compression excludeUserAgents property
Closes gh-3363
This commit is contained in:
		
							parent
							
								
									5e243b28db
								
							
						
					
					
						commit
						5f25080091
					
				| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue