Skip to content

Commit df5f591

Browse files
committed
Support Jetty 10
Closes gh-24886
1 parent a95e93a commit df5f591

File tree

11 files changed

+175
-45
lines changed

11 files changed

+175
-45
lines changed

spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ Spring Boot supports the following embedded servlet containers:
5959
| Jetty 9.4
6060
| 3.1
6161

62+
| Jetty 10.0
63+
| 4.0
64+
6265
| Undertow 2.0
6366
| 4.0
6467
|===

spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,8 @@ The following Maven example shows how to exclude Tomcat and include Jetty for Sp
451451
</dependency>
452452
----
453453

454-
NOTE: The version of the Servlet API has been overridden as, unlike Tomcat 9 and Undertow 2.0, Jetty 9.4 does not support Servlet 4.0.
454+
NOTE: The version of the Servlet API has been overridden as, unlike Tomcat 9 and Undertow 2, Jetty 9.4 does not support Servlet 4.0.
455+
If you wish to use Jetty 10, which does support Servlet 4.0, override the `jetty.version` property rather than the `servlet-api.version` property.
455456

456457
The following Gradle example shows how to use Undertow in place of Reactor Netty for Spring WebFlux:
457458

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/GracefulShutdown.java

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,7 +16,10 @@
1616

1717
package org.springframework.boot.web.embedded.jetty;
1818

19+
import java.lang.reflect.Method;
20+
import java.util.concurrent.CompletableFuture;
1921
import java.util.concurrent.ExecutionException;
22+
import java.util.concurrent.Future;
2023
import java.util.function.Supplier;
2124

2225
import org.apache.commons.logging.Log;
@@ -27,6 +30,7 @@
2730
import org.springframework.boot.web.server.GracefulShutdownCallback;
2831
import org.springframework.boot.web.server.GracefulShutdownResult;
2932
import org.springframework.core.log.LogMessage;
33+
import org.springframework.util.ReflectionUtils;
3034

3135
/**
3236
* Handles Jetty graceful shutdown.
@@ -50,23 +54,44 @@ final class GracefulShutdown {
5054

5155
void shutDownGracefully(GracefulShutdownCallback callback) {
5256
logger.info("Commencing graceful shutdown. Waiting for active requests to complete");
57+
boolean jetty10 = isJetty10();
5358
for (Connector connector : this.server.getConnectors()) {
54-
shutdown(connector);
59+
shutdown(connector, !jetty10);
5560
}
5661
this.shuttingDown = true;
5762
new Thread(() -> awaitShutdown(callback), "jetty-shutdown").start();
5863

5964
}
6065

61-
private void shutdown(Connector connector) {
66+
@SuppressWarnings("unchecked")
67+
private void shutdown(Connector connector, boolean getResult) {
68+
Future<Void> result;
6269
try {
63-
connector.shutdown().get();
70+
result = connector.shutdown();
6471
}
65-
catch (InterruptedException ex) {
66-
Thread.currentThread().interrupt();
72+
catch (NoSuchMethodError ex) {
73+
Method shutdown = ReflectionUtils.findMethod(connector.getClass(), "shutdown");
74+
result = (Future<Void>) ReflectionUtils.invokeMethod(shutdown, connector);
75+
}
76+
if (getResult) {
77+
try {
78+
result.get();
79+
}
80+
catch (InterruptedException ex) {
81+
Thread.currentThread().interrupt();
82+
}
83+
catch (ExecutionException ex) {
84+
// Continue
85+
}
86+
}
87+
}
88+
89+
private boolean isJetty10() {
90+
try {
91+
return CompletableFuture.class.equals(Connector.class.getMethod("shutdown").getReturnType());
6792
}
68-
catch (ExecutionException ex) {
69-
// Continue
93+
catch (Exception ex) {
94+
return false;
7095
}
7196
}
7297

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyEmbeddedErrorHandler.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
2121
import java.util.HashSet;
2222
import java.util.Set;
2323

24+
import javax.servlet.ServletException;
2425
import javax.servlet.http.HttpServletRequest;
2526
import javax.servlet.http.HttpServletResponse;
2627

@@ -49,11 +50,11 @@ public boolean errorPageForMethod(String method) {
4950

5051
@Override
5152
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
52-
throws IOException {
53+
throws IOException, ServletException {
5354
if (!HANDLED_HTTP_METHODS.contains(baseRequest.getMethod())) {
5455
baseRequest.setMethod("GET");
5556
}
56-
super.doError(target, baseRequest, request, response);
57+
super.handle(target, baseRequest, request, response);
5758
}
5859

5960
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyHandlerWrappers.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -47,7 +47,12 @@ static HandlerWrapper createGzipHandlerWrapper(Compression compression) {
4747
handler.addIncludedMethods(httpMethod.name());
4848
}
4949
if (compression.getExcludedUserAgents() != null) {
50-
handler.setExcludedAgentPatterns(compression.getExcludedUserAgents());
50+
try {
51+
handler.setExcludedAgentPatterns(compression.getExcludedUserAgents());
52+
}
53+
catch (NoSuchMethodError ex) {
54+
// Jetty 10 does not support User-Agent-based exclusions
55+
}
5156
}
5257
return handler;
5358
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.io.File;
2020
import java.io.IOException;
2121
import java.io.InputStream;
22+
import java.lang.reflect.Method;
2223
import java.net.InetSocketAddress;
2324
import java.net.MalformedURLException;
2425
import java.net.URL;
@@ -70,6 +71,7 @@
7071
import org.springframework.context.ResourceLoaderAware;
7172
import org.springframework.core.io.ResourceLoader;
7273
import org.springframework.util.Assert;
74+
import org.springframework.util.ReflectionUtils;
7375
import org.springframework.util.StringUtils;
7476

7577
/**
@@ -306,7 +308,16 @@ protected final void addDefaultServlet(WebAppContext context) {
306308
holder.setInitParameter("dirAllowed", "false");
307309
holder.setInitOrder(1);
308310
context.getServletHandler().addServletWithMapping(holder, "/");
309-
context.getServletHandler().getServletMapping("/").setDefault(true);
311+
ServletMapping servletMapping = context.getServletHandler().getServletMapping("/");
312+
try {
313+
servletMapping.setDefault(true);
314+
}
315+
catch (NoSuchMethodError ex) {
316+
// Jetty 10
317+
Method setFromDefaultDescriptor = ReflectionUtils.findMethod(servletMapping.getClass(),
318+
"setFromDefaultDescriptor", boolean.class);
319+
ReflectionUtils.invokeMethod(setFromDefaultDescriptor, servletMapping, true);
320+
}
310321
}
311322

312323
/**

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.eclipse.jetty.server.handler.HandlerCollection;
3333
import org.eclipse.jetty.server.handler.HandlerWrapper;
3434
import org.eclipse.jetty.server.handler.StatisticsHandler;
35-
import org.eclipse.jetty.util.component.AbstractLifeCycle;
3635

3736
import org.springframework.boot.web.server.GracefulShutdownCallback;
3837
import org.springframework.boot.web.server.GracefulShutdownResult;
@@ -120,18 +119,7 @@ private void initialize() {
120119
// Cache the connectors and then remove them to prevent requests being
121120
// handled before the application context is ready.
122121
this.connectors = this.server.getConnectors();
123-
this.server.addBean(new AbstractLifeCycle() {
124-
125-
@Override
126-
protected void doStart() throws Exception {
127-
for (Connector connector : JettyWebServer.this.connectors) {
128-
Assert.state(connector.isStopped(),
129-
() -> "Connector " + connector + " has been started prematurely");
130-
}
131-
JettyWebServer.this.server.setConnectors(null);
132-
}
133-
134-
});
122+
JettyWebServer.this.server.setConnectors(null);
135123
// Start the server so that the ServletContext is available
136124
this.server.start();
137125
this.server.setStopAtShutdown(false);

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -106,8 +106,22 @@ private ServerConnector createServerConnector(Server server, SslContextFactory.S
106106
private ServerConnector createHttp11ServerConnector(Server server, HttpConfiguration config,
107107
SslContextFactory.Server sslContextFactory) {
108108
HttpConnectionFactory connectionFactory = new HttpConnectionFactory(config);
109-
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory,
110-
HttpVersion.HTTP_1_1.asString());
109+
SslConnectionFactory sslConnectionFactory;
110+
try {
111+
sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString());
112+
}
113+
catch (NoSuchMethodError ex) {
114+
// Jetty 10
115+
try {
116+
sslConnectionFactory = SslConnectionFactory.class
117+
.getConstructor(SslContextFactory.Server.class, String.class)
118+
.newInstance(sslContextFactory, HttpVersion.HTTP_1_1.asString());
119+
}
120+
catch (Exception ex2) {
121+
throw new RuntimeException(ex2);
122+
}
123+
}
124+
111125
return new SslValidatingServerConnector(server, sslContextFactory, this.ssl.getKeyAlias(), sslConnectionFactory,
112126
connectionFactory);
113127
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2012-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.web.embedded.jetty;
18+
19+
import org.eclipse.jetty.server.handler.ErrorHandler;
20+
import org.junit.jupiter.api.Disabled;
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.condition.EnabledForJreRange;
23+
import org.junit.jupiter.api.condition.JRE;
24+
25+
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
26+
import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
30+
@EnabledForJreRange(min = JRE.JAVA_11)
31+
@ClassPathExclusions({ "jetty-*.jar", "tomcat-embed*.jar" })
32+
@ClassPathOverrides({ "org.slf4j:slf4j-api:1.7.25", "org.eclipse.jetty:jetty-io:10.0.0",
33+
"org.eclipse.jetty:jetty-server:10.0.0", "org.eclipse.jetty:jetty-servlet:10.0.0",
34+
"org.eclipse.jetty:jetty-util:10.0.0", "org.eclipse.jetty:jetty-webapp:10.0.0",
35+
"org.eclipse.jetty.http2:http2-common:10.0.0", "org.eclipse.jetty.http2:http2-hpack:10.0.0",
36+
"org.eclipse.jetty.http2:http2-server:10.0.0", "org.mortbay.jasper:apache-jsp:8.5.40" })
37+
public class Jetty10ServletWebServerFactoryTests extends JettyServletWebServerFactoryTests {
38+
39+
@Override
40+
@Test
41+
protected void correctVersionOfJettyUsed() {
42+
String jettyVersion = ErrorHandler.class.getPackage().getImplementationVersion();
43+
assertThat(jettyVersion.startsWith("10.0"));
44+
}
45+
46+
@Override
47+
@Disabled("Jetty 10 does not support User-Agent-based compression")
48+
protected void noCompressionForUserAgent() {
49+
50+
}
51+
52+
@Override
53+
@Disabled("Jetty 10 adds methods to Configuration that we can't mock while compiling against 9")
54+
protected void jettyConfigurations() throws Exception {
55+
}
56+
57+
}

0 commit comments

Comments
 (0)