Skip to content

Commit 7266d48

Browse files
committed
Merge branch '3.0.x' into 3.1.x
Closes gh-36009
2 parents d3522a7 + 39c3827 commit 7266d48

File tree

3 files changed

+239
-1
lines changed

3 files changed

+239
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2012-2023 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.autoconfigure.websocket.reactive;
18+
19+
import jakarta.servlet.ServletContext;
20+
import org.eclipse.jetty.server.Handler;
21+
import org.eclipse.jetty.server.handler.HandlerCollection;
22+
import org.eclipse.jetty.server.handler.HandlerWrapper;
23+
import org.eclipse.jetty.servlet.ServletContextHandler;
24+
import org.eclipse.jetty.websocket.core.server.WebSocketMappings;
25+
import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents;
26+
import org.eclipse.jetty.websocket.jakarta.server.internal.JakartaWebSocketServerContainer;
27+
import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer;
28+
import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
29+
30+
import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory;
31+
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
32+
import org.springframework.core.Ordered;
33+
34+
/**
35+
* WebSocket customizer for {@link JettyReactiveWebServerFactory}.
36+
*
37+
* @author Andy Wilkinson
38+
* @since 3.0.8
39+
*/
40+
public class JettyWebSocketReactiveWebServerCustomizer
41+
implements WebServerFactoryCustomizer<JettyReactiveWebServerFactory>, Ordered {
42+
43+
@Override
44+
public void customize(JettyReactiveWebServerFactory factory) {
45+
factory.addServerCustomizers((server) -> {
46+
ServletContextHandler servletContextHandler = findServletContextHandler(server);
47+
if (servletContextHandler != null) {
48+
ServletContext servletContext = servletContextHandler.getServletContext();
49+
if (JettyWebSocketServerContainer.getContainer(servletContext) == null) {
50+
WebSocketServerComponents.ensureWebSocketComponents(server, servletContext);
51+
JettyWebSocketServerContainer.ensureContainer(servletContext);
52+
}
53+
if (JakartaWebSocketServerContainer.getContainer(servletContext) == null) {
54+
WebSocketServerComponents.ensureWebSocketComponents(server, servletContext);
55+
WebSocketUpgradeFilter.ensureFilter(servletContext);
56+
WebSocketMappings.ensureMappings(servletContext);
57+
JakartaWebSocketServerContainer.ensureContainer(servletContext);
58+
}
59+
}
60+
});
61+
}
62+
63+
private ServletContextHandler findServletContextHandler(Handler handler) {
64+
if (handler instanceof ServletContextHandler servletContextHandler) {
65+
return servletContextHandler;
66+
}
67+
if (handler instanceof HandlerWrapper handlerWrapper) {
68+
return findServletContextHandler(handlerWrapper.getHandler());
69+
}
70+
if (handler instanceof HandlerCollection handlerCollection) {
71+
for (Handler contained : handlerCollection.getHandlers()) {
72+
ServletContextHandler servletContextHandler = findServletContextHandler(contained);
73+
if (servletContextHandler != null) {
74+
return servletContextHandler;
75+
}
76+
}
77+
}
78+
return null;
79+
}
80+
81+
@Override
82+
public int getOrder() {
83+
return 0;
84+
}
85+
86+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/reactive/WebSocketReactiveAutoConfiguration.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2023 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.
@@ -20,6 +20,7 @@
2020
import jakarta.websocket.server.ServerContainer;
2121
import org.apache.catalina.startup.Tomcat;
2222
import org.apache.tomcat.websocket.server.WsSci;
23+
import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
2324

2425
import org.springframework.boot.autoconfigure.AutoConfiguration;
2526
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -57,4 +58,16 @@ TomcatWebSocketReactiveWebServerCustomizer websocketReactiveWebServerCustomizer(
5758

5859
}
5960

61+
@Configuration(proxyBeanMethods = false)
62+
@ConditionalOnClass(JakartaWebSocketServletContainerInitializer.class)
63+
static class JettyWebSocketConfiguration {
64+
65+
@Bean
66+
@ConditionalOnMissingBean(name = "websocketReactiveWebServerCustomizer")
67+
JettyWebSocketReactiveWebServerCustomizer websocketServletWebServerCustomizer() {
68+
return new JettyWebSocketReactiveWebServerCustomizer();
69+
}
70+
71+
}
72+
6073
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright 2012-2023 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.autoconfigure.websocket.reactive;
18+
19+
import java.util.function.Function;
20+
import java.util.stream.Stream;
21+
22+
import jakarta.servlet.ServletContext;
23+
import jakarta.websocket.server.ServerContainer;
24+
import org.apache.catalina.Container;
25+
import org.apache.catalina.Context;
26+
import org.apache.catalina.startup.Tomcat;
27+
import org.eclipse.jetty.servlet.ServletContextHandler;
28+
import org.junit.jupiter.params.ParameterizedTest;
29+
import org.junit.jupiter.params.provider.Arguments;
30+
import org.junit.jupiter.params.provider.MethodSource;
31+
32+
import org.springframework.boot.testsupport.classpath.ForkedClassPath;
33+
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
34+
import org.springframework.boot.testsupport.web.servlet.Servlet5ClassPathOverrides;
35+
import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory;
36+
import org.springframework.boot.web.embedded.jetty.JettyWebServer;
37+
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
38+
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
39+
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
40+
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
41+
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
42+
import org.springframework.context.annotation.Bean;
43+
import org.springframework.context.annotation.Configuration;
44+
import org.springframework.http.server.reactive.HttpHandler;
45+
46+
import static org.assertj.core.api.Assertions.assertThat;
47+
48+
/**
49+
* Tests for {@link WebSocketReactiveAutoConfiguration}.
50+
*
51+
* @author Andy Wilkinson
52+
*/
53+
@DirtiesUrlFactories
54+
class WebSocketReactiveAutoConfigurationTests {
55+
56+
@ParameterizedTest(name = "{0}")
57+
@MethodSource("testConfiguration")
58+
@ForkedClassPath
59+
void serverContainerIsAvailableFromTheServletContext(String server,
60+
Function<AnnotationConfigReactiveWebServerApplicationContext, ServletContext> servletContextAccessor,
61+
Class<?>... configuration) {
62+
try (AnnotationConfigReactiveWebServerApplicationContext context = new AnnotationConfigReactiveWebServerApplicationContext(
63+
configuration)) {
64+
Object serverContainer = servletContextAccessor.apply(context)
65+
.getAttribute("jakarta.websocket.server.ServerContainer");
66+
assertThat(serverContainer).isInstanceOf(ServerContainer.class);
67+
}
68+
}
69+
70+
static Stream<Arguments> testConfiguration() {
71+
return Stream.of(Arguments.of("Jetty",
72+
(Function<AnnotationConfigReactiveWebServerApplicationContext, ServletContext>) WebSocketReactiveAutoConfigurationTests::getJettyServletContext,
73+
new Class<?>[] { JettyConfiguration.class,
74+
WebSocketReactiveAutoConfiguration.JettyWebSocketConfiguration.class }),
75+
Arguments.of("Tomcat",
76+
(Function<AnnotationConfigReactiveWebServerApplicationContext, ServletContext>) WebSocketReactiveAutoConfigurationTests::getTomcatServletContext,
77+
new Class<?>[] { TomcatConfiguration.class,
78+
WebSocketReactiveAutoConfiguration.TomcatWebSocketConfiguration.class }));
79+
}
80+
81+
private static ServletContext getJettyServletContext(AnnotationConfigReactiveWebServerApplicationContext context) {
82+
return ((ServletContextHandler) ((JettyWebServer) context.getWebServer()).getServer().getHandler())
83+
.getServletContext();
84+
}
85+
86+
private static ServletContext getTomcatServletContext(AnnotationConfigReactiveWebServerApplicationContext context) {
87+
return findContext(((TomcatWebServer) context.getWebServer()).getTomcat()).getServletContext();
88+
}
89+
90+
private static Context findContext(Tomcat tomcat) {
91+
for (Container child : tomcat.getHost().findChildren()) {
92+
if (child instanceof Context context) {
93+
return context;
94+
}
95+
}
96+
throw new IllegalStateException("The host does not contain a Context");
97+
}
98+
99+
@Configuration(proxyBeanMethods = false)
100+
static class CommonConfiguration {
101+
102+
@Bean
103+
static WebServerFactoryCustomizerBeanPostProcessor webServerFactoryCustomizerBeanPostProcessor() {
104+
return new WebServerFactoryCustomizerBeanPostProcessor();
105+
}
106+
107+
@Bean
108+
HttpHandler echoHandler() {
109+
return (request, response) -> response.writeWith(request.getBody());
110+
}
111+
112+
}
113+
114+
@Configuration(proxyBeanMethods = false)
115+
static class TomcatConfiguration extends CommonConfiguration {
116+
117+
@Bean
118+
ReactiveWebServerFactory webServerFactory() {
119+
TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory();
120+
factory.setPort(0);
121+
return factory;
122+
}
123+
124+
}
125+
126+
@Servlet5ClassPathOverrides
127+
@Configuration(proxyBeanMethods = false)
128+
static class JettyConfiguration extends CommonConfiguration {
129+
130+
@Bean
131+
ReactiveWebServerFactory webServerFactory() {
132+
JettyReactiveWebServerFactory factory = new JettyReactiveWebServerFactory();
133+
factory.setPort(0);
134+
return factory;
135+
}
136+
137+
}
138+
139+
}

0 commit comments

Comments
 (0)