From 15bc718248da7440bfeda7d2f8cf23c19b524d33 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 11 Jan 2018 11:15:17 +0100 Subject: [PATCH] Apply server.jetty.* config to reactive servers This commit applies `server.jetty.*` configuration properties to Jetty when configured as a reactive web server. It also removes some infrastructure support for Jetty 8, which is not supported anymore in Spring Boot 2.0 (partial fix for gh-11504). See gh-11500 --- .../web/embedded/jetty/JettyCustomizer.java | 184 ++++++++++++++++++ .../web/embedded/jetty/package-info.java | 22 +++ ...ultReactiveWebServerFactoryCustomizer.java | 6 + ...aultServletWebServerFactoryCustomizer.java | 162 +-------------- ...activeWebServerFactoryCustomizerTests.java | 100 ++++++++++ .../src/checkstyle/import-control.xml | 3 + .../ConfigurableJettyWebServerFactory.java | 56 ++++++ .../jetty/ForwardHeadersCustomizer.java | 45 +++++ .../jetty/JettyReactiveWebServerFactory.java | 94 ++++----- .../jetty/JettyServletWebServerFactory.java | 49 +---- 10 files changed, 470 insertions(+), 251 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/jetty/JettyCustomizer.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/jetty/package-info.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ForwardHeadersCustomizer.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/jetty/JettyCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/jetty/JettyCustomizer.java new file mode 100644 index 00000000000..2974ca449da --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/jetty/JettyCustomizer.java @@ -0,0 +1,184 @@ +/* + * Copyright 2012-2018 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.embedded.jetty; + +import java.time.Duration; + +import org.eclipse.jetty.server.AbstractConnector; +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.NCSARequestLog; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerWrapper; + +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.cloud.CloudPlatform; +import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory; +import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; +import org.springframework.core.env.Environment; + +/** + * Customization for Jetty-specific features common + * for both Servlet and Reactive servers. + * + * @author Brian Clozel + * @since 2.0.0 + */ +public final class JettyCustomizer { + + private JettyCustomizer() { + } + + public static void customizeJetty(ServerProperties serverProperties, + Environment environment, ConfigurableJettyWebServerFactory factory) { + ServerProperties.Jetty jettyProperties = serverProperties.getJetty(); + factory.setUseForwardHeaders( + getOrDeduceUseForwardHeaders(serverProperties, environment)); + if (jettyProperties.getAcceptors() != null) { + factory.setAcceptors(jettyProperties.getAcceptors()); + } + if (jettyProperties.getSelectors() != null) { + factory.setSelectors(jettyProperties.getSelectors()); + } + if (serverProperties.getMaxHttpHeaderSize() > 0) { + customizeMaxHttpHeaderSize(factory, + serverProperties.getMaxHttpHeaderSize()); + } + if (jettyProperties.getMaxHttpPostSize() > 0) { + customizeMaxHttpPostSize(factory, jettyProperties.getMaxHttpPostSize()); + } + + if (serverProperties.getConnectionTimeout() != null) { + customizeConnectionTimeout(factory, + serverProperties.getConnectionTimeout()); + } + if (jettyProperties.getAccesslog().isEnabled()) { + customizeAccessLog(factory, jettyProperties.getAccesslog()); + } + } + + private static boolean getOrDeduceUseForwardHeaders(ServerProperties serverProperties, + Environment environment) { + if (serverProperties.isUseForwardHeaders() != null) { + return serverProperties.isUseForwardHeaders(); + } + CloudPlatform platform = CloudPlatform.getActive(environment); + return platform != null && platform.isUsingForwardHeaders(); + } + + private static void customizeConnectionTimeout( + ConfigurableJettyWebServerFactory factory, Duration connectionTimeout) { + factory.addServerCustomizers((server) -> { + for (org.eclipse.jetty.server.Connector connector : server + .getConnectors()) { + if (connector instanceof AbstractConnector) { + ((AbstractConnector) connector) + .setIdleTimeout(connectionTimeout.toMillis()); + } + } + }); + } + + private static void customizeMaxHttpHeaderSize( + ConfigurableJettyWebServerFactory factory, int maxHttpHeaderSize) { + factory.addServerCustomizers(new JettyServerCustomizer() { + + @Override + public void customize(Server server) { + for (org.eclipse.jetty.server.Connector connector : server + .getConnectors()) { + for (ConnectionFactory connectionFactory : connector + .getConnectionFactories()) { + if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) { + customize( + (HttpConfiguration.ConnectionFactory) connectionFactory); + } + } + } + } + + private void customize(HttpConfiguration.ConnectionFactory factory) { + HttpConfiguration configuration = factory.getHttpConfiguration(); + configuration.setRequestHeaderSize(maxHttpHeaderSize); + configuration.setResponseHeaderSize(maxHttpHeaderSize); + } + + }); + } + + private static void customizeMaxHttpPostSize(ConfigurableJettyWebServerFactory factory, + int maxHttpPostSize) { + factory.addServerCustomizers(new JettyServerCustomizer() { + + @Override + public void customize(Server server) { + setHandlerMaxHttpPostSize(maxHttpPostSize, server.getHandlers()); + } + + private void setHandlerMaxHttpPostSize(int maxHttpPostSize, + Handler... handlers) { + for (Handler handler : handlers) { + if (handler instanceof ContextHandler) { + ((ContextHandler) handler) + .setMaxFormContentSize(maxHttpPostSize); + } + else if (handler instanceof HandlerWrapper) { + setHandlerMaxHttpPostSize(maxHttpPostSize, + ((HandlerWrapper) handler).getHandler()); + } + else if (handler instanceof HandlerCollection) { + setHandlerMaxHttpPostSize(maxHttpPostSize, + ((HandlerCollection) handler).getHandlers()); + } + } + } + + }); + } + + private static void customizeAccessLog(ConfigurableJettyWebServerFactory factory, + ServerProperties.Jetty.Accesslog properties) { + factory.addServerCustomizers((server) -> { + NCSARequestLog log = new NCSARequestLog(); + if (properties.getFilename() != null) { + log.setFilename(properties.getFilename()); + } + if (properties.getFileDateFormat() != null) { + log.setFilenameDateFormat(properties.getFileDateFormat()); + } + log.setRetainDays(properties.getRetentionPeriod()); + log.setAppend(properties.isAppend()); + log.setExtended(properties.isExtendedFormat()); + if (properties.getDateFormat() != null) { + log.setLogDateFormat(properties.getDateFormat()); + } + if (properties.getLocale() != null) { + log.setLogLocale(properties.getLocale()); + } + if (properties.getTimeZone() != null) { + log.setLogTimeZone(properties.getTimeZone().getID()); + } + log.setLogCookies(properties.isLogCookies()); + log.setLogServer(properties.isLogServer()); + log.setLogLatency(properties.isLogLatency()); + server.setRequestLog(log); + }); + } +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/jetty/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/jetty/package-info.java new file mode 100644 index 00000000000..48e2b24335f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/jetty/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2012-2018 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. + */ + +/** + * Configuration for embedded reactive and servlet Jetty web servers. + * + * @see org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory + */ +package org.springframework.boot.autoconfigure.web.embedded.jetty; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizer.java index 34810f85ac2..e1aa7a6ecf6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizer.java @@ -17,7 +17,9 @@ package org.springframework.boot.autoconfigure.web.reactive; import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.autoconfigure.web.embedded.jetty.JettyCustomizer; import org.springframework.boot.autoconfigure.web.embedded.tomcat.TomcatCustomizer; +import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; @@ -73,6 +75,10 @@ public class DefaultReactiveWebServerFactoryCustomizer implements TomcatCustomizer.customizeTomcat(this.serverProperties, this.environment, (TomcatReactiveWebServerFactory) factory); } + if (factory instanceof JettyReactiveWebServerFactory) { + JettyCustomizer.customizeJetty(this.serverProperties, this.environment, + (JettyReactiveWebServerFactory) factory); + } } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java index 64caaf30fa2..c26b5b4b59e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java @@ -25,21 +25,12 @@ import javax.servlet.ServletException; import javax.servlet.SessionCookieConfig; import io.undertow.UndertowOptions; -import org.eclipse.jetty.server.AbstractConnector; -import org.eclipse.jetty.server.ConnectionFactory; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.NCSARequestLog; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; -import org.eclipse.jetty.server.handler.HandlerWrapper; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties.Session; +import org.springframework.boot.autoconfigure.web.embedded.jetty.JettyCustomizer; import org.springframework.boot.autoconfigure.web.embedded.tomcat.TomcatCustomizer; import org.springframework.boot.cloud.CloudPlatform; -import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; @@ -128,7 +119,6 @@ public class DefaultServletWebServerFactoryCustomizer JettyCustomizer.customizeJetty(this.serverProperties, this.environment, (JettyServletWebServerFactory) factory); } - if (factory instanceof UndertowServletWebServerFactory) { UndertowCustomizer.customizeUndertow(this.serverProperties, this.environment, (UndertowServletWebServerFactory) factory); @@ -308,154 +298,4 @@ public class DefaultServletWebServerFactoryCustomizer } - private static class JettyCustomizer { - - public static void customizeJetty(ServerProperties serverProperties, - Environment environment, JettyServletWebServerFactory factory) { - ServerProperties.Jetty jettyProperties = serverProperties.getJetty(); - factory.setUseForwardHeaders( - getOrDeduceUseForwardHeaders(serverProperties, environment)); - if (jettyProperties.getAcceptors() != null) { - factory.setAcceptors(jettyProperties.getAcceptors()); - } - if (jettyProperties.getSelectors() != null) { - factory.setSelectors(jettyProperties.getSelectors()); - } - if (serverProperties.getMaxHttpHeaderSize() > 0) { - customizeMaxHttpHeaderSize(factory, - serverProperties.getMaxHttpHeaderSize()); - } - if (jettyProperties.getMaxHttpPostSize() > 0) { - customizeMaxHttpPostSize(factory, jettyProperties.getMaxHttpPostSize()); - } - - if (serverProperties.getConnectionTimeout() != null) { - customizeConnectionTimeout(factory, - serverProperties.getConnectionTimeout()); - } - if (jettyProperties.getAccesslog().isEnabled()) { - customizeAccessLog(factory, jettyProperties.getAccesslog()); - } - } - - private static void customizeConnectionTimeout( - JettyServletWebServerFactory factory, Duration connectionTimeout) { - factory.addServerCustomizers((server) -> { - for (org.eclipse.jetty.server.Connector connector : server - .getConnectors()) { - if (connector instanceof AbstractConnector) { - ((AbstractConnector) connector) - .setIdleTimeout(connectionTimeout.toMillis()); - } - } - }); - } - - private static void customizeMaxHttpHeaderSize( - JettyServletWebServerFactory factory, int maxHttpHeaderSize) { - factory.addServerCustomizers(new JettyServerCustomizer() { - - @Override - public void customize(Server server) { - for (org.eclipse.jetty.server.Connector connector : server - .getConnectors()) { - try { - for (ConnectionFactory connectionFactory : connector - .getConnectionFactories()) { - if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) { - customize( - (HttpConfiguration.ConnectionFactory) connectionFactory); - } - } - } - catch (NoSuchMethodError ex) { - customizeOnJetty8(connector, maxHttpHeaderSize); - } - } - - } - - private void customize(HttpConfiguration.ConnectionFactory factory) { - HttpConfiguration configuration = factory.getHttpConfiguration(); - configuration.setRequestHeaderSize(maxHttpHeaderSize); - configuration.setResponseHeaderSize(maxHttpHeaderSize); - } - - private void customizeOnJetty8( - org.eclipse.jetty.server.Connector connector, - int maxHttpHeaderSize) { - try { - connector.getClass().getMethod("setRequestHeaderSize", int.class) - .invoke(connector, maxHttpHeaderSize); - connector.getClass().getMethod("setResponseHeaderSize", int.class) - .invoke(connector, maxHttpHeaderSize); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - }); - } - - private static void customizeMaxHttpPostSize(JettyServletWebServerFactory factory, - int maxHttpPostSize) { - factory.addServerCustomizers(new JettyServerCustomizer() { - - @Override - public void customize(Server server) { - setHandlerMaxHttpPostSize(maxHttpPostSize, server.getHandlers()); - } - - private void setHandlerMaxHttpPostSize(int maxHttpPostSize, - Handler... handlers) { - for (Handler handler : handlers) { - if (handler instanceof ContextHandler) { - ((ContextHandler) handler) - .setMaxFormContentSize(maxHttpPostSize); - } - else if (handler instanceof HandlerWrapper) { - setHandlerMaxHttpPostSize(maxHttpPostSize, - ((HandlerWrapper) handler).getHandler()); - } - else if (handler instanceof HandlerCollection) { - setHandlerMaxHttpPostSize(maxHttpPostSize, - ((HandlerCollection) handler).getHandlers()); - } - } - } - - }); - } - - private static void customizeAccessLog(JettyServletWebServerFactory factory, - ServerProperties.Jetty.Accesslog properties) { - factory.addServerCustomizers((server) -> { - NCSARequestLog log = new NCSARequestLog(); - if (properties.getFilename() != null) { - log.setFilename(properties.getFilename()); - } - if (properties.getFileDateFormat() != null) { - log.setFilenameDateFormat(properties.getFileDateFormat()); - } - log.setRetainDays(properties.getRetentionPeriod()); - log.setAppend(properties.isAppend()); - log.setExtended(properties.isExtendedFormat()); - if (properties.getDateFormat() != null) { - log.setLogDateFormat(properties.getDateFormat()); - } - if (properties.getLocale() != null) { - log.setLogLocale(properties.getLocale()); - } - if (properties.getTimeZone() != null) { - log.setLogTimeZone(properties.getTimeZone().getID()); - } - log.setLogCookies(properties.isLogCookies()); - log.setLogServer(properties.isLogServer()); - log.setLogLatency(properties.isLogLatency()); - server.setRequestLog(log); - }); - } - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizerTests.java index a3d158ae095..fbaaf3e4aba 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizerTests.java @@ -16,9 +16,13 @@ package org.springframework.boot.autoconfigure.web.reactive; +import java.io.File; +import java.io.IOException; import java.net.InetAddress; import java.util.HashMap; +import java.util.Locale; import java.util.Map; +import java.util.TimeZone; import org.apache.catalina.Context; import org.apache.catalina.Valve; @@ -26,6 +30,8 @@ import org.apache.catalina.startup.Tomcat; import org.apache.catalina.valves.AccessLogValve; import org.apache.catalina.valves.RemoteIpValve; import org.apache.coyote.AbstractProtocol; +import org.eclipse.jetty.server.NCSARequestLog; +import org.eclipse.jetty.server.RequestLog; import org.junit.Before; import org.junit.Test; @@ -34,6 +40,8 @@ import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; +import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory; +import org.springframework.boot.web.embedded.jetty.JettyWebServer; import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory; @@ -42,6 +50,7 @@ import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; /** @@ -340,6 +349,97 @@ public class DefaultReactiveWebServerFactoryCustomizerTests { } } + @Test + public void defaultUseForwardHeadersJetty() { + JettyReactiveWebServerFactory factory = spy(new JettyReactiveWebServerFactory()); + this.customizer.customize(factory); + verify(factory).setUseForwardHeaders(false); + } + + @Test + public void setUseForwardHeadersJetty() { + this.properties.setUseForwardHeaders(true); + JettyReactiveWebServerFactory factory = spy(new JettyReactiveWebServerFactory()); + this.customizer.customize(factory); + verify(factory).setUseForwardHeaders(true); + } + + @Test + public void deduceUseForwardHeadersJetty() { + this.customizer.setEnvironment(new MockEnvironment().withProperty("DYNO", "-")); + JettyReactiveWebServerFactory factory = spy(new JettyReactiveWebServerFactory()); + this.customizer.customize(factory); + verify(factory).setUseForwardHeaders(true); + } + + @Test + public void jettyAccessLogCanBeEnabled() { + JettyReactiveWebServerFactory factory = new JettyReactiveWebServerFactory(0); + Map map = new HashMap<>(); + map.put("server.jetty.accesslog.enabled", "true"); + bindProperties(map); + this.customizer.customize(factory); + JettyWebServer webServer = (JettyWebServer) factory.getWebServer(mock(HttpHandler.class)); + try { + NCSARequestLog requestLog = getNCSARequestLog(webServer); + assertThat(requestLog.getFilename()).isNull(); + assertThat(requestLog.isAppend()).isFalse(); + assertThat(requestLog.isExtended()).isFalse(); + assertThat(requestLog.getLogCookies()).isFalse(); + assertThat(requestLog.getLogServer()).isFalse(); + assertThat(requestLog.getLogLatency()).isFalse(); + } + finally { + webServer.stop(); + } + } + + @Test + public void jettyAccessLogCanBeCustomized() throws IOException { + File logFile = File.createTempFile("jetty_log", ".log"); + JettyReactiveWebServerFactory factory = new JettyReactiveWebServerFactory(0); + Map map = new HashMap<>(); + String timezone = TimeZone.getDefault().getID(); + map.put("server.jetty.accesslog.enabled", "true"); + map.put("server.jetty.accesslog.filename", logFile.getAbsolutePath()); + map.put("server.jetty.accesslog.file-date-format", "yyyy-MM-dd"); + map.put("server.jetty.accesslog.retention-period", "42"); + map.put("server.jetty.accesslog.append", "true"); + map.put("server.jetty.accesslog.extended-format", "true"); + map.put("server.jetty.accesslog.date-format", "HH:mm:ss"); + map.put("server.jetty.accesslog.locale", "en_BE"); + map.put("server.jetty.accesslog.time-zone", timezone); + map.put("server.jetty.accesslog.log-cookies", "true"); + map.put("server.jetty.accesslog.log-server", "true"); + map.put("server.jetty.accesslog.log-latency", "true"); + bindProperties(map); + this.customizer.customize(factory); + JettyWebServer webServer = (JettyWebServer) factory.getWebServer(mock(HttpHandler.class)); + NCSARequestLog requestLog = getNCSARequestLog(webServer); + try { + assertThat(requestLog.getFilename()).isEqualTo(logFile.getAbsolutePath()); + assertThat(requestLog.getFilenameDateFormat()).isEqualTo("yyyy-MM-dd"); + assertThat(requestLog.getRetainDays()).isEqualTo(42); + assertThat(requestLog.isAppend()).isTrue(); + assertThat(requestLog.isExtended()).isTrue(); + assertThat(requestLog.getLogDateFormat()).isEqualTo("HH:mm:ss"); + assertThat(requestLog.getLogLocale()).isEqualTo(new Locale("en", "BE")); + assertThat(requestLog.getLogTimeZone()).isEqualTo(timezone); + assertThat(requestLog.getLogCookies()).isTrue(); + assertThat(requestLog.getLogServer()).isTrue(); + assertThat(requestLog.getLogLatency()).isTrue(); + } + finally { + webServer.stop(); + } + } + + private NCSARequestLog getNCSARequestLog(JettyWebServer webServer) { + RequestLog requestLog = webServer.getServer().getRequestLog(); + assertThat(requestLog).isInstanceOf(NCSARequestLog.class); + return (NCSARequestLog) requestLog; + } + private void bindProperties(Map map) { ConfigurationPropertySource source = new MapConfigurationPropertySource(map); new Binder(source).bind("server", Bindable.ofInstance(this.properties)); diff --git a/spring-boot-project/spring-boot-parent/src/checkstyle/import-control.xml b/spring-boot-project/spring-boot-parent/src/checkstyle/import-control.xml index 9b011196365..9722ef6576d 100644 --- a/spring-boot-project/spring-boot-parent/src/checkstyle/import-control.xml +++ b/spring-boot-project/spring-boot-parent/src/checkstyle/import-control.xml @@ -14,6 +14,9 @@ + + + diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java new file mode 100644 index 00000000000..47010fbc734 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2018 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.web.embedded.jetty; + +import org.eclipse.jetty.server.Server; + +/** + * Web Server Factory configuration for Jetty-specific features. + * + * @author Brian Clozel + * @since 2.0.0 + * @see JettyServletWebServerFactory + * @see JettyReactiveWebServerFactory + */ +public interface ConfigurableJettyWebServerFactory { + + /** + * Set the number of acceptor threads to use. + * @param acceptors the number of acceptor threads to use + */ + void setAcceptors(int acceptors); + + /** + * Set the number of selector threads to use. + * @param selectors the number of selector threads to use + */ + void setSelectors(int selectors); + + /** + * Set if x-forward-* headers should be processed. + * @param useForwardHeaders if x-forward headers should be used + */ + void setUseForwardHeaders(boolean useForwardHeaders); + + /** + * Add {@link JettyServerCustomizer}s that will be applied to the {@link Server} + * before it is started. + * @param customizers the customizers to add + */ + void addServerCustomizers(JettyServerCustomizer... customizers); + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ForwardHeadersCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ForwardHeadersCustomizer.java new file mode 100644 index 00000000000..7b40e50b28f --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ForwardHeadersCustomizer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2018 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.web.embedded.jetty; + +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.ForwardedRequestCustomizer; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Server; + +/** + * {@link JettyServerCustomizer} to add {@link ForwardedRequestCustomizer}. + * @author Phillip Webb + */ +class ForwardHeadersCustomizer implements JettyServerCustomizer { + + @Override + public void customize(Server server) { + ForwardedRequestCustomizer customizer = new ForwardedRequestCustomizer(); + for (Connector connector : server.getConnectors()) { + for (ConnectionFactory connectionFactory : connector + .getConnectionFactories()) { + if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) { + ((HttpConfiguration.ConnectionFactory) connectionFactory) + .getHttpConfiguration().addCustomizer(customizer); + } + } + } + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java index b9b82f8abe1..6daed875cf9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java @@ -46,7 +46,8 @@ import org.springframework.util.Assert; * @author Brian Clozel * @since 2.0.0 */ -public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory { +public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory + implements ConfigurableJettyWebServerFactory { private static final Log logger = LogFactory .getLog(JettyReactiveWebServerFactory.class); @@ -61,6 +62,8 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact */ private int selectors = -1; + private boolean useForwardHeaders; + private List jettyServerCustomizers = new ArrayList<>(); private ThreadPool threadPool; @@ -80,6 +83,16 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact super(port); } + @Override + public void setUseForwardHeaders(boolean useForwardHeaders) { + this.useForwardHeaders = useForwardHeaders; + } + + @Override + public void setAcceptors(int acceptors) { + this.acceptors = acceptors; + } + @Override public WebServer getWebServer(HttpHandler httpHandler) { JettyHttpHandlerAdapter servlet = new JettyHttpHandlerAdapter(httpHandler); @@ -87,6 +100,37 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact return new JettyWebServer(server, getPort() >= 0); } + @Override + public void addServerCustomizers(JettyServerCustomizer... customizers) { + Assert.notNull(customizers, "Customizers must not be null"); + this.jettyServerCustomizers.addAll(Arrays.asList(customizers)); + } + + /** + * Sets {@link JettyServerCustomizer}s that will be applied to the {@link Server} + * before it is started. Calling this method will replace any existing customizers. + * @param customizers the Jetty customizers to apply + */ + public void setServerCustomizers( + Collection customizers) { + Assert.notNull(customizers, "Customizers must not be null"); + this.jettyServerCustomizers = new ArrayList<>(customizers); + } + + /** + * Returns a mutable collection of Jetty {@link JettyServerCustomizer}s that will be + * applied to the {@link Server} before it is created. + * @return the Jetty customizers + */ + public Collection getServerCustomizers() { + return this.jettyServerCustomizers; + } + + @Override + public void setSelectors(int selectors) { + this.selectors = selectors; + } + protected Server createJettyServer(JettyHttpHandlerAdapter servlet) { int port = (getPort() >= 0 ? getPort() : 0); InetSocketAddress address = new InetSocketAddress(getAddress(), port); @@ -104,6 +148,9 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact for (JettyServerCustomizer customizer : getServerCustomizers()) { customizer.customize(server); } + if (this.useForwardHeaders) { + new ForwardHeadersCustomizer().customize(server); + } return server; } @@ -143,49 +190,4 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact this.threadPool = threadPool; } - /** - * Set the number of acceptor threads to use. - * @param acceptors the number of acceptor threads to use - */ - public void setAcceptors(int acceptors) { - this.acceptors = acceptors; - } - - /** - * Sets {@link JettyServerCustomizer}s that will be applied to the {@link Server} - * before it is started. Calling this method will replace any existing customizers. - * @param customizers the Jetty customizers to apply - */ - public void setServerCustomizers( - Collection customizers) { - Assert.notNull(customizers, "Customizers must not be null"); - this.jettyServerCustomizers = new ArrayList<>(customizers); - } - - /** - * Returns a mutable collection of Jetty {@link JettyServerCustomizer}s that will be - * applied to the {@link Server} before it is created. - * @return the Jetty customizers - */ - public Collection getServerCustomizers() { - return this.jettyServerCustomizers; - } - - /** - * Add {@link JettyServerCustomizer}s that will be applied to the {@link Server} - * before it is started. - * @param customizers the customizers to add - */ - public void addServerCustomizers(JettyServerCustomizer... customizers) { - Assert.notNull(customizers, "Customizers must not be null"); - this.jettyServerCustomizers.addAll(Arrays.asList(customizers)); - } - - /** - * Set the number of selector threads to use. - * @param selectors the number of selector threads to use - */ - public void setSelectors(int selectors) { - this.selectors = selectors; - } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java index b0084fc7435..812ab9c11e9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java @@ -40,7 +40,6 @@ import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.ForwardedRequestCustomizer; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Request; @@ -96,7 +95,7 @@ import org.springframework.util.StringUtils; * @see JettyWebServer */ public class JettyServletWebServerFactory extends AbstractServletWebServerFactory - implements ResourceLoaderAware { + implements ConfigurableJettyWebServerFactory, ResourceLoaderAware { private List configurations = new ArrayList<>(); @@ -438,29 +437,17 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor this.resourceLoader = resourceLoader; } - /** - * Set if x-forward-* headers should be processed. - * @param useForwardHeaders if x-forward headers should be used - * @since 1.3.0 - */ + @Override public void setUseForwardHeaders(boolean useForwardHeaders) { this.useForwardHeaders = useForwardHeaders; } - /** - * Set the number of acceptor threads to use. - * @param acceptors the number of acceptor threads to use - * @since 1.4.0 - */ + @Override public void setAcceptors(int acceptors) { this.acceptors = acceptors; } - /** - * Set the number of selector threads to use. - * @param selectors the number of selector threads to use - * @since 1.4.0 - */ + @Override public void setSelectors(int selectors) { this.selectors = selectors; } @@ -485,11 +472,7 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor return this.jettyServerCustomizers; } - /** - * Add {@link JettyServerCustomizer}s that will be applied to the {@link Server} - * before it is started. - * @param customizers the customizers to add - */ + @Override public void addServerCustomizers(JettyServerCustomizer... customizers) { Assert.notNull(customizers, "Customizers must not be null"); this.jettyServerCustomizers.addAll(Arrays.asList(customizers)); @@ -565,28 +548,6 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor } } - /** - * {@link JettyServerCustomizer} to add {@link ForwardedRequestCustomizer}. Only - * supported with Jetty 9 (hence the inner class) - */ - private static class ForwardHeadersCustomizer implements JettyServerCustomizer { - - @Override - public void customize(Server server) { - ForwardedRequestCustomizer customizer = new ForwardedRequestCustomizer(); - for (Connector connector : server.getConnectors()) { - for (ConnectionFactory connectionFactory : connector - .getConnectionFactories()) { - if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) { - ((HttpConfiguration.ConnectionFactory) connectionFactory) - .getHttpConfiguration().addCustomizer(customizer); - } - } - } - } - - } - /** * {@link HandlerWrapper} to add a custom {@code server} header. */