Skip to content

Commit 2046629

Browse files
committed
Add WebSocketClient and WebSocketConnectionManager
This change adds a WebSocketClient abstraction and enables the use of WebSocketHandler on the client side.
1 parent ab5d60d commit 2046629

18 files changed

+793
-231
lines changed

spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.websocket;
1818

1919
import org.apache.commons.logging.Log;
20+
import org.apache.commons.logging.LogFactory;
2021
import org.springframework.beans.BeansException;
2122
import org.springframework.beans.factory.BeanFactory;
2223
import org.springframework.beans.factory.BeanFactoryAware;
@@ -32,14 +33,14 @@
3233
*/
3334
public class HandlerProvider<T> implements BeanFactoryAware {
3435

36+
private Log logger = LogFactory.getLog(this.getClass());
37+
3538
private final T handlerBean;
3639

3740
private final Class<? extends T> handlerClass;
3841

3942
private AutowireCapableBeanFactory beanFactory;
4043

41-
private Log logger;
42-
4344

4445
public HandlerProvider(T handlerBean) {
4546
Assert.notNull(handlerBean, "handlerBean is required");
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2002-2013 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+
* http://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+
package org.springframework.websocket;
17+
18+
import java.net.URI;
19+
20+
import org.springframework.http.HttpHeaders;
21+
22+
23+
/**
24+
*
25+
* @author Rossen Stoyanchev
26+
* @since 4.0
27+
*/
28+
public class WebSocketHandshakeRequest {
29+
30+
private final URI uri;
31+
32+
private final HttpHeaders headers;
33+
34+
35+
public WebSocketHandshakeRequest(HttpHeaders headers, URI uri) {
36+
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
37+
this.uri = uri;
38+
}
39+
40+
public URI getUri() {
41+
return this.uri;
42+
}
43+
44+
public HttpHeaders getHeaders() {
45+
return this.headers;
46+
}
47+
48+
}
Lines changed: 20 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,18 @@
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
1212
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
1716
package org.springframework.websocket.client;
1817

19-
import java.io.IOException;
2018
import java.net.URI;
2119

22-
import javax.websocket.ContainerProvider;
23-
import javax.websocket.DeploymentException;
24-
import javax.websocket.Session;
25-
import javax.websocket.WebSocketContainer;
26-
2720
import org.apache.commons.logging.Log;
2821
import org.apache.commons.logging.LogFactory;
2922
import org.springframework.context.SmartLifecycle;
@@ -37,7 +30,7 @@
3730
* @author Rossen Stoyanchev
3831
* @since 4.0
3932
*/
40-
public abstract class AbstractEndpointConnectionManager implements SmartLifecycle {
33+
public abstract class AbstractWebSocketConnectionManager implements SmartLifecycle {
4134

4235
protected final Log logger = LogFactory.getLog(getClass());
4336

@@ -47,35 +40,15 @@ public abstract class AbstractEndpointConnectionManager implements SmartLifecycl
4740

4841
private int phase = Integer.MAX_VALUE;
4942

50-
private final WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer();
51-
52-
private Session session;
53-
5443
private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("EndpointConnectionManager-");
5544

5645
private final Object lifecycleMonitor = new Object();
5746

5847

59-
public AbstractEndpointConnectionManager(String uriTemplate, Object... uriVariables) {
48+
public AbstractWebSocketConnectionManager(String uriTemplate, Object... uriVariables) {
6049
this.uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode().toUri();
6150
}
6251

63-
public void setAsyncSendTimeout(long timeoutInMillis) {
64-
this.webSocketContainer.setAsyncSendTimeout(timeoutInMillis);
65-
}
66-
67-
public void setMaxSessionIdleTimeout(long timeoutInMillis) {
68-
this.webSocketContainer.setDefaultMaxSessionIdleTimeout(timeoutInMillis);
69-
}
70-
71-
public void setMaxTextMessageBufferSize(int bufferSize) {
72-
this.webSocketContainer.setDefaultMaxTextMessageBufferSize(bufferSize);
73-
}
74-
75-
public void setMaxBinaryMessageBufferSize(Integer bufferSize) {
76-
this.webSocketContainer.setDefaultMaxBinaryMessageBufferSize(bufferSize);
77-
}
78-
7952
/**
8053
* Set whether to auto-connect to the remote endpoint after this connection manager
8154
* has been initialized and the Spring context has been refreshed.
@@ -117,27 +90,24 @@ protected URI getUri() {
11790
return this.uri;
11891
}
11992

120-
protected WebSocketContainer getWebSocketContainer() {
121-
return this.webSocketContainer;
122-
}
123-
12493
/**
125-
* Auto-connects to the configured {@link #setDefaultUri(URI) default URI}.
94+
* Connect to the configured {@link #setDefaultUri(URI) default URI}. If already
95+
* connected, the method has no impact.
12696
*/
127-
public void start() {
97+
public final void start() {
12898
synchronized (this.lifecycleMonitor) {
12999
if (!isRunning()) {
130100
this.taskExecutor.execute(new Runnable() {
131101
@Override
132102
public void run() {
133103
synchronized (lifecycleMonitor) {
134104
try {
135-
logger.info("Connecting to endpoint at URI " + uri);
136-
session = connect();
105+
logger.info("Connecting to WebSocket at " + uri);
106+
openConnection();
137107
logger.info("Successfully connected");
138108
}
139109
catch (Throwable ex) {
140-
logger.error("Failed to connect to endpoint at " + uri, ex);
110+
logger.error("Failed to connect", ex);
141111
}
142112
}
143113
}
@@ -146,25 +116,26 @@ public void run() {
146116
}
147117
}
148118

149-
protected abstract Session connect() throws DeploymentException, IOException;
119+
protected abstract void openConnection() throws Exception;
150120

151121
/**
152-
* Deactivates the configured message endpoint.
122+
* Closes the configured message WebSocket connection.
153123
*/
154-
public void stop() {
124+
public final void stop() {
155125
synchronized (this.lifecycleMonitor) {
156126
if (isRunning()) {
157127
try {
158-
this.session.close();
128+
closeConnection();
159129
}
160-
catch (IOException e) {
161-
// ignore
130+
catch (Throwable e) {
131+
logger.error("Failed to stop WebSocket connection", e);
162132
}
163133
}
164-
this.session = null;
165134
}
166135
}
167136

137+
protected abstract void closeConnection() throws Exception;
138+
168139
public void stop(Runnable callback) {
169140
synchronized (this.lifecycleMonitor) {
170141
this.stop();
@@ -177,12 +148,10 @@ public void stop(Runnable callback) {
177148
*/
178149
public boolean isRunning() {
179150
synchronized (this.lifecycleMonitor) {
180-
if ((this.session != null) && this.session.isOpen()) {
181-
return true;
182-
}
183-
this.session = null;
184-
return false;
151+
return isConnected();
185152
}
186153
}
187154

155+
protected abstract boolean isConnected();
156+
188157
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2002-2013 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+
* http://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+
package org.springframework.websocket.client;
17+
18+
import java.net.URI;
19+
20+
import org.springframework.http.HttpHeaders;
21+
import org.springframework.websocket.WebSocketHandler;
22+
import org.springframework.websocket.WebSocketSession;
23+
24+
25+
/**
26+
* Contract for starting a WebSocket handshake request.
27+
*
28+
* <p>To automatically start a WebSocket connection when the application starts, see
29+
* {@link WebSocketConnectionManager}.
30+
*
31+
* @author Rossen Stoyanchev
32+
* @since 4.0
33+
*
34+
* @see WebSocketConnectionManager
35+
*/
36+
public interface WebSocketClient {
37+
38+
39+
WebSocketSession doHandshake(WebSocketHandler handler, String uriTemplate, Object... uriVariables)
40+
throws WebSocketConnectFailureException;
41+
42+
WebSocketSession doHandshake(WebSocketHandler handler, URI uri)
43+
throws WebSocketConnectFailureException;
44+
45+
WebSocketSession doHandshake(WebSocketHandler handler, HttpHeaders headers, URI uri)
46+
throws WebSocketConnectFailureException;
47+
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2002-2013 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+
* http://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+
package org.springframework.websocket.client;
17+
18+
import org.springframework.core.NestedRuntimeException;
19+
20+
21+
/**
22+
*
23+
* @author Rossen Stoyanchev
24+
* @since 4.0
25+
*/
26+
@SuppressWarnings("serial")
27+
public class WebSocketConnectFailureException extends NestedRuntimeException {
28+
29+
30+
public WebSocketConnectFailureException(String msg, Throwable cause) {
31+
super(msg, cause);
32+
}
33+
34+
public WebSocketConnectFailureException(String msg) {
35+
super(msg);
36+
}
37+
38+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2002-2013 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+
* http://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+
package org.springframework.websocket.client;
17+
18+
import java.util.List;
19+
20+
import org.springframework.http.HttpHeaders;
21+
import org.springframework.websocket.HandlerProvider;
22+
import org.springframework.websocket.WebSocketHandler;
23+
import org.springframework.websocket.WebSocketSession;
24+
25+
26+
/**
27+
*
28+
* @author Rossen Stoyanchev
29+
* @since 4.0
30+
*/
31+
public class WebSocketConnectionManager extends AbstractWebSocketConnectionManager {
32+
33+
private final WebSocketClient client;
34+
35+
private final HandlerProvider<WebSocketHandler> webSocketHandlerProvider;
36+
37+
private WebSocketSession webSocketSession;
38+
39+
private List<String> subProtocols;
40+
41+
42+
public WebSocketConnectionManager(WebSocketClient webSocketClient,
43+
WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables) {
44+
45+
super(uriTemplate, uriVariables);
46+
this.webSocketHandlerProvider = new HandlerProvider<WebSocketHandler>(webSocketHandler);
47+
this.client = webSocketClient;
48+
}
49+
50+
public void setSubProtocols(List<String> subProtocols) {
51+
this.subProtocols = subProtocols;
52+
}
53+
54+
public List<String> getSubProtocols() {
55+
return this.subProtocols;
56+
}
57+
58+
@Override
59+
protected void openConnection() throws Exception {
60+
WebSocketHandler webSocketHandler = this.webSocketHandlerProvider.getHandler();
61+
HttpHeaders headers = new HttpHeaders();
62+
headers.setSecWebSocketProtocol(this.subProtocols);
63+
this.webSocketSession = this.client.doHandshake(webSocketHandler, headers, getUri());
64+
}
65+
66+
@Override
67+
protected void closeConnection() throws Exception {
68+
this.webSocketSession.close();
69+
}
70+
71+
@Override
72+
protected boolean isConnected() {
73+
return ((this.webSocketSession != null) && (this.webSocketSession.isOpen()));
74+
}
75+
76+
}

0 commit comments

Comments
 (0)