From 4bb5baac65e3bfa29fbf5a1136d90ab7967fe83d Mon Sep 17 00:00:00 2001 From: olivier lamy Date: Thu, 17 Aug 2017 17:02:00 +1000 Subject: [PATCH 1/2] add dumpAfterStart configuration to log all the Jetty configuration after start Signed-off-by: olivier lamy --- .../boot/autoconfigure/web/ServerProperties.java | 13 +++++++++++++ .../DefaultServletWebServerFactoryCustomizer.java | 1 + .../asciidoc/appendix-application-properties.adoc | 1 + .../jetty/JettyServletWebServerFactory.java | 14 ++++++++++++++ 4 files changed, 29 insertions(+) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index 45e86d0f685c..b4f79a36295d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -907,6 +907,11 @@ public static class Jetty { */ private Integer selectors; + /** + * Dump jetty configuration in the logs after starting. + */ + private boolean dumpAfterStart = false; + public Accesslog getAccesslog() { return this.accesslog; } @@ -935,6 +940,14 @@ public void setSelectors(Integer selectors) { this.selectors = selectors; } + public boolean isDumpAfterStart() { + return this.dumpAfterStart; + } + + public void setDumpAfterStart(boolean dumpAfterStart) { + this.dumpAfterStart = dumpAfterStart; + } + /** * Jetty access log properties. */ diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java index 31e711a88ae7..3914711e63d7 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java @@ -480,6 +480,7 @@ public static void customizeJetty(final ServerProperties serverProperties, if (jettyProperties.getAccesslog().isEnabled()) { customizeAccessLog(factory, jettyProperties.getAccesslog()); } + factory.setDumpAfterStart(jettyProperties.isDumpAfterStart()); } private static void customizeConnectionTimeout( diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 868eed9ea25f..aaed6032f131 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -162,6 +162,7 @@ content into your application; rather pick only the properties that you need. server.error.path=/error # Path of the error controller. server.error.whitelabel.enabled=true # Enable the default error page displayed in browsers in case of a server error. server.jetty.acceptors= # Number of acceptor threads to use. + server.jetty.dumpAfterStart= false # log all the Jetty configuration in the logs after start server.jetty.accesslog.append=false # Append to log. server.jetty.accesslog.date-format=dd/MMM/yyyy:HH:mm:ss Z # Timestamp format of the request log. server.jetty.accesslog.enabled=false # Enable access log. diff --git a/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java b/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java index 1129d3a58075..4d9634888a1d 100644 --- a/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java @@ -122,6 +122,11 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor */ private int selectors = -1; + /** + * Dump jetty configuration in the logs after starting. + */ + private boolean dumpAfterStart = false; + private List jettyServerCustomizers = new ArrayList<>(); private ResourceLoader resourceLoader; @@ -182,6 +187,7 @@ public WebServer getWebServer(ServletContextInitializer... initializers) { private Server createServer(InetSocketAddress address) { Server server = new Server(getThreadPool()); server.setConnectors(new Connector[] { createConnector(address, server) }); + server.setDumpAfterStart(this.dumpAfterStart); return server; } @@ -578,6 +584,14 @@ public void setSelectors(int selectors) { this.selectors = selectors; } + /** + * To dump the Jetty configuration in the logs. + * @param dumpAfterStart true to dump configuration + */ + public void setDumpAfterStart(boolean dumpAfterStart) { + this.dumpAfterStart = dumpAfterStart; + } + /** * Sets {@link JettyServerCustomizer}s that will be applied to the {@link Server} * before it is started. Calling this method will replace any existing customizers. From 47328ba327d03f140da7c9c1bead2e229e9e7cef Mon Sep 17 00:00:00 2001 From: olivier lamy Date: Fri, 18 Aug 2017 16:58:40 +1000 Subject: [PATCH 2/2] initial work on Jetty HTTP/2 support, wip to discuss configuration format Signed-off-by: olivier lamy --- .../autoconfigure/web/ServerProperties.java | 14 +++ ...aultServletWebServerFactoryCustomizer.java | 4 + spring-boot-dependencies/pom.xml | 11 +++ .../spring-boot-starter-jetty/pom.xml | 16 ++++ spring-boot/pom.xml | 20 ++++ .../jetty/JettyServletWebServerFactory.java | 51 ++++++++++ .../AbstractConfigurableWebServerFactory.java | 11 +++ .../server/ConfigurableWebServerFactory.java | 6 ++ .../boot/web/server/Http2.java | 94 +++++++++++++++++++ 9 files changed, 227 insertions(+) create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/server/Http2.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index b4f79a36295d..86b8edb3728a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -31,6 +31,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.web.server.Compression; +import org.springframework.boot.web.server.Http2; import org.springframework.boot.web.server.Ssl; import org.springframework.boot.web.servlet.server.Jsp; import org.springframework.util.Assert; @@ -102,6 +103,9 @@ public class ServerProperties { @NestedConfigurationProperty private Compression compression = new Compression(); + @NestedConfigurationProperty + private Http2 http2; + private Servlet servlet = new Servlet(); private final Tomcat tomcat = new Tomcat(); @@ -210,6 +214,14 @@ public Undertow getUndertow() { return this.undertow; } + public Http2 getHttp2() { + return this.http2; + } + + public void setHttp2(Http2 http2) { + this.http2 = http2; + } + /** * Servlet properties. */ @@ -948,6 +960,8 @@ public void setDumpAfterStart(boolean dumpAfterStart) { this.dumpAfterStart = dumpAfterStart; } + + /** * Jetty access log properties. */ diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java index 3914711e63d7..81e1ef7064c9 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java @@ -112,6 +112,9 @@ public void customize(ConfigurableServletWebServerFactory factory) { if (this.serverProperties.getSsl() != null) { factory.setSsl(this.serverProperties.getSsl()); } + if (this.serverProperties.getHttp2() != null) { + factory.setHttp2(this.serverProperties.getHttp2()); + } if (this.serverProperties.getServlet() != null) { factory.setJsp(this.serverProperties.getServlet().getJsp()); } @@ -480,6 +483,7 @@ public static void customizeJetty(final ServerProperties serverProperties, if (jettyProperties.getAccesslog().isEnabled()) { customizeAccessLog(factory, jettyProperties.getAccesslog()); } + factory.setDumpAfterStart(jettyProperties.isDumpAfterStart()); } diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 39928ef8b8f2..1a66b5778985 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -111,6 +111,7 @@ 2.25.1 5.3.2 9.4.6.v20170531 + 1.1.3.v20160715 2.2.0.v201112011158 8.5.9.1 1.13 @@ -1577,6 +1578,16 @@ http2-server ${jetty.version} + + org.eclipse.jetty + jetty-alpn-server + ${jetty.version} + + + org.eclipse.jetty.alpn + alpn-api + ${alpn.api.version} + org.eclipse.jetty.orbit javax.servlet.jsp diff --git a/spring-boot-starters/spring-boot-starter-jetty/pom.xml b/spring-boot-starters/spring-boot-starter-jetty/pom.xml index 424526a8df42..23913bde6338 100644 --- a/spring-boot-starters/spring-boot-starter-jetty/pom.xml +++ b/spring-boot-starters/spring-boot-starter-jetty/pom.xml @@ -27,6 +27,22 @@ org.eclipse.jetty jetty-webapp + + org.eclipse.jetty.http2 + http2-hpack + + + org.eclipse.jetty.http2 + http2-server + + + org.eclipse.jetty + jetty-alpn-server + + + org.eclipse.jetty.alpn + alpn-api + org.eclipse.jetty.websocket websocket-server diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml index 21d22b2c0e74..b9376eaf845d 100644 --- a/spring-boot/pom.xml +++ b/spring-boot/pom.xml @@ -159,6 +159,26 @@ jetty-webapp true + + org.eclipse.jetty.http2 + http2-hpack + true + + + org.eclipse.jetty.http2 + http2-server + true + + + org.eclipse.jetty + jetty-alpn-server + true + + + org.eclipse.jetty.alpn + alpn-api + true + org.glassfish.jersey.core jersey-server diff --git a/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java b/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java index 4d9634888a1d..b55f4d60b4b5 100644 --- a/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java @@ -35,9 +35,13 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.alpn.ALPN; +import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http2.HTTP2Cipher; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Connector; @@ -45,6 +49,7 @@ import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.NegotiatingServerConnectionFactory; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; @@ -181,6 +186,15 @@ public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.useForwardHeaders) { new ForwardHeadersCustomizer().customize(server); } + if (getHttp2() != null && getHttp2().isEnabled()) { + SslContextFactory sslContextFactory = null; + if (getHttp2().getSsl() != null && getHttp2().getSsl().isEnabled()) { + sslContextFactory = new SslContextFactory(); + configureSsl(sslContextFactory, getHttp2().getSsl()); + } + AbstractConnector connector = createHttp2Connector(server, sslContextFactory); + server.setConnectors(new Connector[] { connector }); + } return getJettyWebServer(server); } @@ -219,6 +233,43 @@ private AbstractConnector createSslConnector(Server server, return serverConnector; } + private AbstractConnector createHttp2Connector(Server server, SslContextFactory sslContextFactory) { + + if (sslContextFactory != null) { + sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR); + } + // HTTPS Configuration + HttpConfiguration https_config = new HttpConfiguration(); + https_config.setSecureScheme("https"); + https_config.setSecurePort(getHttp2().getPort()); + https_config.addCustomizer(new SecureRequestCustomizer()); + + // HTTP/2 Connection Factory + HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(https_config); + NegotiatingServerConnectionFactory.checkProtocolNegotiationAvailable(); + ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(); + alpn.setDefaultProtocol("h2"); + + // HTTP/2 Connector + ServerConnector http2Connector = null; + if (sslContextFactory != null) { + // SSL Connection Factory + SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol()); + http2Connector = new ServerConnector(server, ssl, alpn, h2, new HttpConnectionFactory(https_config)); + } + else { + http2Connector = new ServerConnector(server, alpn, h2, new HttpConnectionFactory(https_config)); + } + + + http2Connector.setPort(getHttp2().getPort()); + //? + //http2Connector.setHost(getHttp2().getAddress().getHostName()); + + ALPN.debug = getHttp2().getAlpnDebug(); + return http2Connector; + } + private Handler addHandlerWrappers(Handler handler) { if (getCompression() != null && getCompression().getEnabled()) { handler = applyWrapper(handler, createGzipHandler()); diff --git a/spring-boot/src/main/java/org/springframework/boot/web/server/AbstractConfigurableWebServerFactory.java b/spring-boot/src/main/java/org/springframework/boot/web/server/AbstractConfigurableWebServerFactory.java index 85ff5e5576ab..ba1ed60fe3ed 100644 --- a/spring-boot/src/main/java/org/springframework/boot/web/server/AbstractConfigurableWebServerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/web/server/AbstractConfigurableWebServerFactory.java @@ -48,6 +48,8 @@ public class AbstractConfigurableWebServerFactory private Ssl ssl; + private Http2 http2; + private SslStoreProvider sslStoreProvider; private Compression compression; @@ -152,6 +154,15 @@ public void setServerHeader(String serverHeader) { this.serverHeader = serverHeader; } + @Override + public void setHttp2(Http2 http2) { + this.http2 = http2; + } + + public Http2 getHttp2() { + return this.http2; + } + /** * Return the absolute temp dir for given web server. * @param prefix server name diff --git a/spring-boot/src/main/java/org/springframework/boot/web/server/ConfigurableWebServerFactory.java b/spring-boot/src/main/java/org/springframework/boot/web/server/ConfigurableWebServerFactory.java index 345441d0e83d..42594d9af5e9 100644 --- a/spring-boot/src/main/java/org/springframework/boot/web/server/ConfigurableWebServerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/web/server/ConfigurableWebServerFactory.java @@ -75,4 +75,10 @@ public interface ConfigurableWebServerFactory */ void setServerHeader(String serverHeader); + /** + * Sets the Http2 configuration that will be applied to the server's default connector. + * @param http2 the {@link Http2} configuration + */ + void setHttp2(Http2 http2); + } diff --git a/spring-boot/src/main/java/org/springframework/boot/web/server/Http2.java b/spring-boot/src/main/java/org/springframework/boot/web/server/Http2.java new file mode 100644 index 000000000000..1ea6897d3df1 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/server/Http2.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012-2017 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.server; + +import java.net.InetAddress; + +/** + * Simple server-independent abstraction for HTTP/2 configuration. + * + * @author Olivier Lamy + * @since 2.0.0 + */ +public class Http2 { + + /** + * Enable HTTP/2 support. + */ + private boolean enabled = false; + + /** + * HTTP/2 port. + */ + private Integer port; + + /** + * Network address to which the HTTP/2 connector should be bind to. + */ + private InetAddress address; + + /** + * Activate alpn debug. + */ + private Boolean alpnDebug; + + /** + * Ssl options for HTTP/2 connector. + */ + private Ssl ssl; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Integer getPort() { + return this.port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public InetAddress getAddress() { + return this.address; + } + + public void setAddress(InetAddress address) { + this.address = address; + } + + public Boolean getAlpnDebug() { + return this.alpnDebug; + } + + public void setAlpnDebug(Boolean alpnDebug) { + this.alpnDebug = alpnDebug; + } + + public Ssl getSsl() { + return this.ssl; + } + + public void setSsl(Ssl ssl) { + this.ssl = ssl; + } + +}