Browse Source
This commit upgrades Spring Framework to Jetty 12.0.1, and Reactive HTTP Client 4.0.0. Closes gh-30698pull/31172/head
Arjen Poutsma
1 year ago
32 changed files with 491 additions and 851 deletions
@ -1,175 +0,0 @@
@@ -1,175 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2022 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 |
||||
* |
||||
* https://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.web.reactive.socket.client; |
||||
|
||||
import java.io.IOException; |
||||
import java.net.URI; |
||||
import java.util.function.Function; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.eclipse.jetty.websocket.api.Session; |
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; |
||||
import reactor.core.publisher.Mono; |
||||
import reactor.core.publisher.Sinks; |
||||
|
||||
import org.springframework.context.Lifecycle; |
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.web.reactive.socket.HandshakeInfo; |
||||
import org.springframework.web.reactive.socket.WebSocketHandler; |
||||
import org.springframework.web.reactive.socket.adapter.ContextWebSocketHandler; |
||||
import org.springframework.web.reactive.socket.adapter.JettyWebSocketHandlerAdapter; |
||||
import org.springframework.web.reactive.socket.adapter.JettyWebSocketSession; |
||||
|
||||
/** |
||||
* A {@link WebSocketClient} implementation for use with Jetty |
||||
* {@link org.eclipse.jetty.websocket.client.WebSocketClient}. |
||||
* Only supported on Jetty 11, superseded by {@link StandardWebSocketClient}. |
||||
* |
||||
* <p><strong>Note: </strong> the Jetty {@code WebSocketClient} requires |
||||
* lifecycle management and must be started and stopped. This is automatically |
||||
* managed when this class is declared as a Spring bean and created with the |
||||
* default constructor. See constructor notes for more details. |
||||
* |
||||
* @author Violeta Georgieva |
||||
* @author Rossen Stoyanchev |
||||
* @author Juergen Hoeller |
||||
* @since 5.0 |
||||
* @deprecated as of 6.0.3, in favor of {@link StandardWebSocketClient} |
||||
*/ |
||||
@Deprecated(since = "6.0.3", forRemoval = true) |
||||
public class JettyWebSocketClient implements WebSocketClient, Lifecycle { |
||||
|
||||
private static final Log logger = LogFactory.getLog(JettyWebSocketClient.class); |
||||
|
||||
private final org.eclipse.jetty.websocket.client.WebSocketClient jettyClient; |
||||
|
||||
private final boolean externallyManaged; |
||||
|
||||
|
||||
/** |
||||
* Default constructor that creates and manages an instance of a Jetty |
||||
* {@link org.eclipse.jetty.websocket.client.WebSocketClient WebSocketClient}. |
||||
* The instance can be obtained with {@link #getJettyClient()} for further |
||||
* configuration. |
||||
* <p><strong>Note: </strong> When this constructor is used {@link Lifecycle} |
||||
* methods of this class are delegated to the Jetty {@code WebSocketClient}. |
||||
*/ |
||||
public JettyWebSocketClient() { |
||||
this.jettyClient = new org.eclipse.jetty.websocket.client.WebSocketClient(); |
||||
this.externallyManaged = false; |
||||
} |
||||
|
||||
/** |
||||
* Constructor that accepts an existing instance of a Jetty |
||||
* {@link org.eclipse.jetty.websocket.client.WebSocketClient WebSocketClient}. |
||||
* <p><strong>Note: </strong> Use of this constructor implies the Jetty |
||||
* {@code WebSocketClient} is externally managed and hence {@link Lifecycle} |
||||
* methods of this class are not delegated to it. |
||||
*/ |
||||
public JettyWebSocketClient(org.eclipse.jetty.websocket.client.WebSocketClient jettyClient) { |
||||
this.jettyClient = jettyClient; |
||||
this.externallyManaged = true; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Return the underlying Jetty {@code WebSocketClient}. |
||||
*/ |
||||
public org.eclipse.jetty.websocket.client.WebSocketClient getJettyClient() { |
||||
return this.jettyClient; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void start() { |
||||
if (!this.externallyManaged) { |
||||
try { |
||||
this.jettyClient.start(); |
||||
} |
||||
catch (Exception ex) { |
||||
throw new IllegalStateException("Failed to start Jetty WebSocketClient", ex); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void stop() { |
||||
if (!this.externallyManaged) { |
||||
try { |
||||
this.jettyClient.stop(); |
||||
} |
||||
catch (Exception ex) { |
||||
throw new IllegalStateException("Error stopping Jetty WebSocketClient", ex); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean isRunning() { |
||||
return this.jettyClient.isRunning(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Mono<Void> execute(URI url, WebSocketHandler handler) { |
||||
return execute(url, new HttpHeaders(), handler); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<Void> execute(URI url, HttpHeaders headers, WebSocketHandler handler) { |
||||
return executeInternal(url, headers, handler); |
||||
} |
||||
|
||||
private Mono<Void> executeInternal(URI url, HttpHeaders headers, WebSocketHandler handler) { |
||||
Sinks.Empty<Void> completionSink = Sinks.empty(); |
||||
return Mono.deferContextual(contextView -> { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Connecting to " + url); |
||||
} |
||||
Object jettyHandler = createHandler( |
||||
url, ContextWebSocketHandler.decorate(handler, contextView), completionSink); |
||||
ClientUpgradeRequest request = new ClientUpgradeRequest(); |
||||
request.setHeaders(headers); |
||||
request.setSubProtocols(handler.getSubProtocols()); |
||||
try { |
||||
this.jettyClient.connect(jettyHandler, url, request); |
||||
return completionSink.asMono(); |
||||
} |
||||
catch (IOException ex) { |
||||
return Mono.error(ex); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private Object createHandler(URI url, WebSocketHandler handler, Sinks.Empty<Void> completion) { |
||||
Function<Session, JettyWebSocketSession> sessionFactory = session -> { |
||||
HandshakeInfo info = createHandshakeInfo(url, session); |
||||
return new JettyWebSocketSession(session, info, DefaultDataBufferFactory.sharedInstance, completion); |
||||
}; |
||||
return new JettyWebSocketHandlerAdapter(handler, sessionFactory); |
||||
} |
||||
|
||||
private HandshakeInfo createHandshakeInfo(URI url, Session jettySession) { |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
headers.putAll(jettySession.getUpgradeResponse().getHeaders()); |
||||
String protocol = headers.getFirst("Sec-WebSocket-Protocol"); |
||||
return new HandshakeInfo(url, headers, Mono.empty(), protocol); |
||||
} |
||||
|
||||
} |
@ -1,174 +0,0 @@
@@ -1,174 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2022 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 |
||||
* |
||||
* https://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.web.socket.client.jetty; |
||||
|
||||
import java.net.URI; |
||||
import java.security.Principal; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.Callable; |
||||
import java.util.concurrent.CompletableFuture; |
||||
import java.util.concurrent.Future; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
import org.eclipse.jetty.websocket.api.Session; |
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; |
||||
import org.eclipse.jetty.websocket.client.WebSocketClient; |
||||
|
||||
import org.springframework.context.Lifecycle; |
||||
import org.springframework.core.task.AsyncTaskExecutor; |
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.concurrent.FutureUtils; |
||||
import org.springframework.web.socket.WebSocketExtension; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.WebSocketSession; |
||||
import org.springframework.web.socket.adapter.jetty.JettyWebSocketHandlerAdapter; |
||||
import org.springframework.web.socket.adapter.jetty.JettyWebSocketSession; |
||||
import org.springframework.web.socket.adapter.jetty.WebSocketToJettyExtensionConfigAdapter; |
||||
import org.springframework.web.socket.client.AbstractWebSocketClient; |
||||
|
||||
/** |
||||
* Initiates WebSocket requests to a WebSocket server programmatically |
||||
* through the Jetty WebSocket API. Only supported on Jetty 11, superseded by |
||||
* {@link org.springframework.web.socket.client.standard.StandardWebSocketClient}. |
||||
* |
||||
* <p>As of 4.1 this class implements {@link Lifecycle} rather than |
||||
* {@link org.springframework.context.SmartLifecycle}. Use |
||||
* {@link org.springframework.web.socket.client.WebSocketConnectionManager |
||||
* WebSocketConnectionManager} instead to auto-start a WebSocket connection. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @author Juergen Hoeller |
||||
* @since 4.0 |
||||
* @deprecated as of 6.0.3, in favor of |
||||
* {@link org.springframework.web.socket.client.standard.StandardWebSocketClient} |
||||
*/ |
||||
@Deprecated(since = "6.0.3", forRemoval = true) |
||||
public class JettyWebSocketClient extends AbstractWebSocketClient implements Lifecycle { |
||||
|
||||
private final org.eclipse.jetty.websocket.client.WebSocketClient client; |
||||
|
||||
@Nullable |
||||
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); |
||||
|
||||
|
||||
/** |
||||
* Default constructor that creates an instance of |
||||
* {@link org.eclipse.jetty.websocket.client.WebSocketClient}. |
||||
*/ |
||||
public JettyWebSocketClient() { |
||||
this.client = new org.eclipse.jetty.websocket.client.WebSocketClient(); |
||||
} |
||||
|
||||
/** |
||||
* Constructor that accepts an existing |
||||
* {@link org.eclipse.jetty.websocket.client.WebSocketClient} instance. |
||||
*/ |
||||
public JettyWebSocketClient(WebSocketClient client) { |
||||
this.client = client; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set an {@link AsyncTaskExecutor} to use when opening connections. |
||||
* <p>If this property is set to {@code null}, calls to any of the |
||||
* {@code doHandshake} methods will block until the connection is established. |
||||
* <p>By default an instance of {@code SimpleAsyncTaskExecutor} is used. |
||||
*/ |
||||
public void setTaskExecutor(@Nullable AsyncTaskExecutor taskExecutor) { |
||||
this.taskExecutor = taskExecutor; |
||||
} |
||||
|
||||
/** |
||||
* Return the configured {@link AsyncTaskExecutor}. |
||||
*/ |
||||
@Nullable |
||||
public AsyncTaskExecutor getTaskExecutor() { |
||||
return this.taskExecutor; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void start() { |
||||
try { |
||||
this.client.start(); |
||||
} |
||||
catch (Exception ex) { |
||||
throw new IllegalStateException("Failed to start Jetty WebSocketClient", ex); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void stop() { |
||||
try { |
||||
this.client.stop(); |
||||
} |
||||
catch (Exception ex) { |
||||
logger.error("Failed to stop Jetty WebSocketClient", ex); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean isRunning() { |
||||
return this.client.isStarted(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public CompletableFuture<WebSocketSession> executeInternal(WebSocketHandler wsHandler, |
||||
HttpHeaders headers, final URI uri, List<String> protocols, |
||||
List<WebSocketExtension> extensions, Map<String, Object> attributes) { |
||||
|
||||
final ClientUpgradeRequest request = new ClientUpgradeRequest(); |
||||
request.setSubProtocols(protocols); |
||||
|
||||
for (WebSocketExtension extension : extensions) { |
||||
request.addExtensions(new WebSocketToJettyExtensionConfigAdapter(extension)); |
||||
} |
||||
|
||||
request.setHeaders(headers); |
||||
|
||||
Principal user = getUser(); |
||||
JettyWebSocketSession wsSession = new JettyWebSocketSession(attributes, user); |
||||
|
||||
Callable<WebSocketSession> connectTask = () -> { |
||||
JettyWebSocketHandlerAdapter adapter = new JettyWebSocketHandlerAdapter(wsHandler, wsSession); |
||||
Future<Session> future = this.client.connect(adapter, uri, request); |
||||
future.get(this.client.getConnectTimeout() + 2000, TimeUnit.MILLISECONDS); |
||||
return wsSession; |
||||
}; |
||||
|
||||
if (this.taskExecutor != null) { |
||||
return FutureUtils.callAsync(connectTask, this.taskExecutor); |
||||
} |
||||
else { |
||||
return FutureUtils.callAsync(connectTask); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Return the user to make available through {@link WebSocketSession#getPrincipal()}. |
||||
* By default, this method returns {@code null} |
||||
*/ |
||||
@Nullable |
||||
protected Principal getUser() { |
||||
return null; |
||||
} |
||||
|
||||
} |
@ -1,9 +0,0 @@
@@ -1,9 +0,0 @@
|
||||
/** |
||||
* Client-side support for the Jetty WebSocket API. |
||||
*/ |
||||
@NonNullApi |
||||
@NonNullFields |
||||
package org.springframework.web.socket.client.jetty; |
||||
|
||||
import org.springframework.lang.NonNullApi; |
||||
import org.springframework.lang.NonNullFields; |
@ -1,125 +0,0 @@
@@ -1,125 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2019 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 |
||||
* |
||||
* https://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.web.socket.client.jetty; |
||||
|
||||
/** |
||||
* Tests for {@link JettyWebSocketClient}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class JettyWebSocketClientTests { |
||||
|
||||
/* TODO: complete upgrade to Jetty 11 |
||||
private JettyWebSocketClient client; |
||||
|
||||
private TestJettyWebSocketServer server; |
||||
|
||||
private String wsUrl; |
||||
|
||||
private WebSocketSession wsSession; |
||||
|
||||
|
||||
@BeforeEach |
||||
public void setup() throws Exception { |
||||
|
||||
this.server = new TestJettyWebSocketServer(new TextWebSocketHandler()); |
||||
this.server.start(); |
||||
|
||||
this.client = new JettyWebSocketClient(); |
||||
this.client.start(); |
||||
|
||||
this.wsUrl = "ws://localhost:" + this.server.getPort() + "/test"; |
||||
} |
||||
|
||||
@AfterEach |
||||
public void teardown() throws Exception { |
||||
this.wsSession.close(); |
||||
this.client.stop(); |
||||
this.server.stop(); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void doHandshake() throws Exception { |
||||
|
||||
WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); |
||||
headers.setSecWebSocketProtocol(Arrays.asList("echo")); |
||||
|
||||
this.wsSession = this.client.doHandshake(new TextWebSocketHandler(), headers, new URI(this.wsUrl)).get(); |
||||
|
||||
assertThat(this.wsSession.getUri().toString()).isEqualTo(this.wsUrl); |
||||
assertThat(this.wsSession.getAcceptedProtocol()).isEqualTo("echo"); |
||||
} |
||||
|
||||
@Test |
||||
public void doHandshakeWithTaskExecutor() throws Exception { |
||||
|
||||
WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); |
||||
headers.setSecWebSocketProtocol(Arrays.asList("echo")); |
||||
|
||||
this.client.setTaskExecutor(new SimpleAsyncTaskExecutor()); |
||||
this.wsSession = this.client.doHandshake(new TextWebSocketHandler(), headers, new URI(this.wsUrl)).get(); |
||||
|
||||
assertThat(this.wsSession.getUri().toString()).isEqualTo(this.wsUrl); |
||||
assertThat(this.wsSession.getAcceptedProtocol()).isEqualTo("echo"); |
||||
} |
||||
|
||||
|
||||
private static class TestJettyWebSocketServer { |
||||
|
||||
private final Server server; |
||||
|
||||
|
||||
public TestJettyWebSocketServer(final WebSocketHandler webSocketHandler) { |
||||
|
||||
this.server = new Server(); |
||||
ServerConnector connector = new ServerConnector(this.server); |
||||
connector.setPort(0); |
||||
|
||||
this.server.addConnector(connector); |
||||
this.server.setHandler(new WebSocketUpgradeHandler() { |
||||
@Override |
||||
public void configure(JettyWebSocketServletFactory factory) { |
||||
factory.setCreator(new JettyWebSocketCreator() { |
||||
@Override |
||||
public Object createWebSocket(JettyServerUpgradeRequest req, JettyServerUpgradeResponse resp) { |
||||
if (!CollectionUtils.isEmpty(req.getSubProtocols())) { |
||||
resp.setAcceptedSubProtocol(req.getSubProtocols().get(0)); |
||||
} |
||||
JettyWebSocketSession session = new JettyWebSocketSession(null, null); |
||||
return new JettyWebSocketHandlerAdapter(webSocketHandler, session); |
||||
} |
||||
}); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public void start() throws Exception { |
||||
this.server.start(); |
||||
} |
||||
|
||||
public void stop() throws Exception { |
||||
this.server.stop(); |
||||
} |
||||
|
||||
public int getPort() { |
||||
return ((ServerConnector) this.server.getConnectors()[0]).getLocalPort(); |
||||
} |
||||
} |
||||
*/ |
||||
|
||||
} |
@ -1,64 +0,0 @@
@@ -1,64 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2022 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 |
||||
* |
||||
* https://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.web.socket.sockjs.client; |
||||
|
||||
import org.eclipse.jetty.client.HttpClient; |
||||
|
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.web.socket.JettyWebSocketTestServer; |
||||
import org.springframework.web.socket.server.RequestUpgradeStrategy; |
||||
import org.springframework.web.socket.server.jetty.JettyRequestUpgradeStrategy; |
||||
|
||||
/** |
||||
* SockJS integration tests using Jetty for client and server. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
class JettySockJsIntegrationTests extends AbstractSockJsIntegrationTests { |
||||
|
||||
@Override |
||||
protected Class<?> upgradeStrategyConfigClass() { |
||||
return JettyTestConfig.class; |
||||
} |
||||
|
||||
@Override |
||||
protected JettyWebSocketTestServer createWebSocketTestServer() { |
||||
return new JettyWebSocketTestServer(); |
||||
} |
||||
|
||||
@SuppressWarnings("removal") |
||||
@Override |
||||
protected Transport createWebSocketTransport() { |
||||
return new WebSocketTransport(new org.springframework.web.socket.client.jetty.JettyWebSocketClient()); |
||||
} |
||||
|
||||
@Override |
||||
protected AbstractXhrTransport createXhrTransport() { |
||||
return new JettyXhrTransport(new HttpClient()); |
||||
} |
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class JettyTestConfig { |
||||
@Bean |
||||
RequestUpgradeStrategy upgradeStrategy() { |
||||
return new JettyRequestUpgradeStrategy(); |
||||
} |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue