35 changed files with 1245 additions and 294 deletions
@ -0,0 +1,243 @@
@@ -0,0 +1,243 @@
|
||||
/* |
||||
* Copyright 2002-2013 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.web.socket.support; |
||||
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.util.LinkedCaseInsensitiveMap; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import javax.websocket.Extension; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Locale; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Represents a WebSocket extension as defined in the RFC 6455. |
||||
* WebSocket extensions add protocol features to the WebSocket protocol. The extensions |
||||
* used within a session are negotiated during the handshake phase as follows: |
||||
* <ul> |
||||
* <li>the client may ask for specific extensions in the HTTP handshake request</li> |
||||
* <li>the server responds with the final list of extensions to use in the current session</li> |
||||
* </ul> |
||||
* |
||||
* <p>WebSocket Extension HTTP headers may include parameters and follow |
||||
* <a href="https://tools.ietf.org/html/rfc2616#section-4.2">RFC 2616 Section 4.2</a></p> |
||||
* |
||||
* <p>Note that the order of extensions in HTTP headers defines their order of execution, |
||||
* e.g. extensions "foo, bar" will be executed as "bar(foo(message))".</p> |
||||
* |
||||
* @author Brian Clozel |
||||
* @since 4.0 |
||||
* @see <a href="https://tools.ietf.org/html/rfc6455#section-9"> |
||||
* WebSocket Protocol Extensions, RFC 6455 - Section 9</a> |
||||
*/ |
||||
public class WebSocketExtension { |
||||
|
||||
private final String name; |
||||
|
||||
private final Map<String, String> parameters; |
||||
|
||||
|
||||
/** |
||||
* Create a WebSocketExtension with the given name. |
||||
* |
||||
* @param name the name of the extension |
||||
*/ |
||||
public WebSocketExtension(String name) { |
||||
this(name, null); |
||||
} |
||||
|
||||
/** |
||||
* Create a WebSocketExtension with the given name and parameters. |
||||
* |
||||
* @param name the name of the extension |
||||
* @param parameters the parameters |
||||
*/ |
||||
public WebSocketExtension(String name, Map<String, String> parameters) { |
||||
Assert.hasLength(name, "extension name must not be empty"); |
||||
this.name = name; |
||||
if (!CollectionUtils.isEmpty(parameters)) { |
||||
Map<String, String> m = new LinkedCaseInsensitiveMap<String>(parameters.size(), Locale.ENGLISH); |
||||
m.putAll(parameters); |
||||
this.parameters = Collections.unmodifiableMap(m); |
||||
} |
||||
else { |
||||
this.parameters = Collections.emptyMap(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @return the name of the extension |
||||
*/ |
||||
public String getName() { |
||||
return this.name; |
||||
} |
||||
|
||||
/** |
||||
* @return the parameters of the extension, never {@code null} |
||||
*/ |
||||
public Map<String, String> getParameters() { |
||||
return this.parameters; |
||||
} |
||||
|
||||
/** |
||||
* Parse the given, comma-separated string into a list of {@code WebSocketExtension} objects. |
||||
* <p>This method can be used to parse a "Sec-WebSocket-Extension" extensions. |
||||
* @param extensions the string to parse |
||||
* @return the list of extensions |
||||
* @throws IllegalArgumentException if the string cannot be parsed |
||||
*/ |
||||
public static List<WebSocketExtension> parseExtensions(String extensions) { |
||||
if (extensions == null || !StringUtils.hasText(extensions)) { |
||||
return Collections.emptyList(); |
||||
} |
||||
else { |
||||
List<WebSocketExtension> result = new ArrayList<WebSocketExtension>(); |
||||
for(String token : extensions.split(",")) { |
||||
result.add(parseExtension(token)); |
||||
} |
||||
return result; |
||||
} |
||||
} |
||||
|
||||
private static WebSocketExtension parseExtension(String extension) { |
||||
Assert.doesNotContain(extension, ",", "Expected a single extension value: " + extension); |
||||
String[] parts = StringUtils.tokenizeToStringArray(extension, ";"); |
||||
String name = parts[0].trim(); |
||||
|
||||
Map<String, String> parameters = null; |
||||
if (parts.length > 1) { |
||||
parameters = new LinkedHashMap<String, String>(parts.length - 1); |
||||
for (int i = 1; i < parts.length; i++) { |
||||
String parameter = parts[i]; |
||||
int eqIndex = parameter.indexOf('='); |
||||
if (eqIndex != -1) { |
||||
String attribute = parameter.substring(0, eqIndex); |
||||
String value = parameter.substring(eqIndex + 1, parameter.length()); |
||||
parameters.put(attribute, value); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return new WebSocketExtension(name, parameters); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) { |
||||
return true; |
||||
} |
||||
if ((o == null) || (getClass() != o.getClass())) { |
||||
return false; |
||||
} |
||||
WebSocketExtension that = (WebSocketExtension) o; |
||||
if (!name.equals(that.name)) { |
||||
return false; |
||||
} |
||||
if (!parameters.equals(that.parameters)) { |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
int result = name.hashCode(); |
||||
result = 31 * result + parameters.hashCode(); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
StringBuilder str = new StringBuilder(); |
||||
str.append(this.name); |
||||
for (String param : parameters.keySet()) { |
||||
str.append(';'); |
||||
str.append(param); |
||||
str.append('='); |
||||
str.append(this.parameters.get(param)); |
||||
} |
||||
return str.toString(); |
||||
} |
||||
|
||||
|
||||
// Standard WebSocketExtension adapters
|
||||
|
||||
public static class StandardToWebSocketExtensionAdapter extends WebSocketExtension { |
||||
|
||||
public StandardToWebSocketExtensionAdapter(Extension ext) { |
||||
super(ext.getName()); |
||||
for (Extension.Parameter p : ext.getParameters()) { |
||||
super.getParameters().put(p.getName(), p.getValue()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static class WebSocketToStandardExtensionAdapter implements Extension { |
||||
|
||||
private final String name; |
||||
|
||||
private final List<Parameter> parameters = new ArrayList<Parameter>(); |
||||
|
||||
public WebSocketToStandardExtensionAdapter(final WebSocketExtension ext) { |
||||
this.name = ext.getName(); |
||||
List<Parameter> params = new ArrayList<Parameter>(); |
||||
for (final String paramName : ext.getParameters().keySet()) { |
||||
this.parameters.add(new Parameter() { |
||||
@Override |
||||
public String getName() { |
||||
return paramName; |
||||
} |
||||
@Override |
||||
public String getValue() { |
||||
return ext.getParameters().get(paramName); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
@Override |
||||
public List<Parameter> getParameters() { |
||||
return this.parameters; |
||||
} |
||||
} |
||||
|
||||
// Jetty WebSocketExtension adapters
|
||||
|
||||
public static class WebSocketToJettyExtensionConfigAdapter extends ExtensionConfig { |
||||
|
||||
public WebSocketToJettyExtensionConfigAdapter(WebSocketExtension extension) { |
||||
super(extension.getName()); |
||||
for (Map.Entry<String,String> p : extension.getParameters().entrySet()) { |
||||
super.setParameter(p.getKey(), p.getValue()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,328 @@
@@ -0,0 +1,328 @@
|
||||
/* |
||||
* Copyright 2002-2013 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.web.socket.support; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.util.CollectionUtils; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.*; |
||||
|
||||
/** |
||||
* An {@link org.springframework.http.HttpHeaders} variant that adds support for |
||||
* the HTTP headers defined by the WebSocket specification RFC 6455. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.0 |
||||
*/ |
||||
public class WebSocketHttpHeaders extends HttpHeaders { |
||||
|
||||
public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept"; |
||||
|
||||
public static final String SEC_WEBSOCKET_EXTENSIONS = "Sec-WebSocket-Extensions"; |
||||
|
||||
public static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key"; |
||||
|
||||
public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; |
||||
|
||||
public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version"; |
||||
|
||||
private final HttpHeaders headers; |
||||
|
||||
|
||||
|
||||
|
||||
/** |
||||
* Create a new instance. |
||||
*/ |
||||
public WebSocketHttpHeaders() { |
||||
this(new HttpHeaders(), false); |
||||
} |
||||
|
||||
/** |
||||
* Create an instance that wraps the given pre-existing HttpHeaders and also |
||||
* propagate all changes to it. |
||||
* |
||||
* @param headers the HTTP headers to wrap |
||||
*/ |
||||
public WebSocketHttpHeaders(HttpHeaders headers) { |
||||
this(headers, false); |
||||
} |
||||
|
||||
/** |
||||
* Private constructor that can create read-only {@code WebSocketHttpHeader} instances. |
||||
*/ |
||||
private WebSocketHttpHeaders(HttpHeaders headers, boolean readOnly) { |
||||
this.headers = readOnly ? HttpHeaders.readOnlyHttpHeaders(headers) : headers; |
||||
} |
||||
|
||||
/** |
||||
* Returns {@code WebSocketHttpHeaders} object that can only be read, not written to. |
||||
*/ |
||||
public static WebSocketHttpHeaders readOnlyWebSocketHttpHeaders(WebSocketHttpHeaders headers) { |
||||
return new WebSocketHttpHeaders(headers, true); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Sets the (new) value of the {@code Sec-WebSocket-Accept} header. |
||||
* @param secWebSocketAccept the value of the header |
||||
*/ |
||||
public void setSecWebSocketAccept(String secWebSocketAccept) { |
||||
set(SEC_WEBSOCKET_ACCEPT, secWebSocketAccept); |
||||
} |
||||
|
||||
/** |
||||
* Returns the value of the {@code Sec-WebSocket-Accept} header. |
||||
* @return the value of the header |
||||
*/ |
||||
public String getSecWebSocketAccept() { |
||||
return getFirst(SEC_WEBSOCKET_ACCEPT); |
||||
} |
||||
|
||||
/** |
||||
* Returns the value of the {@code Sec-WebSocket-Extensions} header. |
||||
* @return the value of the header |
||||
*/ |
||||
public List<WebSocketExtension> getSecWebSocketExtensions() { |
||||
List<String> values = get(SEC_WEBSOCKET_EXTENSIONS); |
||||
if (CollectionUtils.isEmpty(values)) { |
||||
return Collections.emptyList(); |
||||
} |
||||
else { |
||||
List<WebSocketExtension> result = new ArrayList<WebSocketExtension>(values.size()); |
||||
for (String value : values) { |
||||
result.addAll(WebSocketExtension.parseExtensions(value)); |
||||
} |
||||
return result; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets the (new) value(s) of the {@code Sec-WebSocket-Extensions} header. |
||||
* @param extensions the values for the header |
||||
*/ |
||||
public void setSecWebSocketExtensions(List<WebSocketExtension> extensions) { |
||||
List<String> result = new ArrayList<String>(extensions.size()); |
||||
for(WebSocketExtension extension : extensions) { |
||||
result.add(extension.toString()); |
||||
} |
||||
set(SEC_WEBSOCKET_EXTENSIONS, toCommaDelimitedString(result)); |
||||
} |
||||
|
||||
/** |
||||
* Sets the (new) value of the {@code Sec-WebSocket-Key} header. |
||||
* @param secWebSocketKey the value of the header |
||||
*/ |
||||
public void setSecWebSocketKey(String secWebSocketKey) { |
||||
set(SEC_WEBSOCKET_KEY, secWebSocketKey); |
||||
} |
||||
|
||||
/** |
||||
* Returns the value of the {@code Sec-WebSocket-Key} header. |
||||
* @return the value of the header |
||||
*/ |
||||
public String getSecWebSocketKey() { |
||||
return getFirst(SEC_WEBSOCKET_KEY); |
||||
} |
||||
|
||||
/** |
||||
* Sets the (new) value of the {@code Sec-WebSocket-Protocol} header. |
||||
* @param secWebSocketProtocol the value of the header |
||||
*/ |
||||
public void setSecWebSocketProtocol(String secWebSocketProtocol) { |
||||
if (secWebSocketProtocol != null) { |
||||
set(SEC_WEBSOCKET_PROTOCOL, secWebSocketProtocol); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets the (new) value of the {@code Sec-WebSocket-Protocol} header. |
||||
* @param secWebSocketProtocols the value of the header |
||||
*/ |
||||
public void setSecWebSocketProtocol(List<String> secWebSocketProtocols) { |
||||
set(SEC_WEBSOCKET_PROTOCOL, toCommaDelimitedString(secWebSocketProtocols)); |
||||
} |
||||
|
||||
/** |
||||
* Returns the value of the {@code Sec-WebSocket-Key} header. |
||||
* @return the value of the header |
||||
*/ |
||||
public List<String> getSecWebSocketProtocol() { |
||||
List<String> values = get(SEC_WEBSOCKET_PROTOCOL); |
||||
if (CollectionUtils.isEmpty(values)) { |
||||
return Collections.emptyList(); |
||||
} |
||||
else if (values.size() == 1) { |
||||
return getFirstValueAsList(SEC_WEBSOCKET_PROTOCOL); |
||||
} |
||||
else { |
||||
return values; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets the (new) value of the {@code Sec-WebSocket-Version} header. |
||||
* @param secWebSocketVersion the value of the header |
||||
*/ |
||||
public void setSecWebSocketVersion(String secWebSocketVersion) { |
||||
set(SEC_WEBSOCKET_VERSION, secWebSocketVersion); |
||||
} |
||||
|
||||
/** |
||||
* Returns the value of the {@code Sec-WebSocket-Version} header. |
||||
* @return the value of the header |
||||
*/ |
||||
public String getSecWebSocketVersion() { |
||||
return getFirst(SEC_WEBSOCKET_VERSION); |
||||
} |
||||
|
||||
|
||||
// Single string methods
|
||||
|
||||
/** |
||||
* Return the first header value for the given header name, if any. |
||||
* @param headerName the header name |
||||
* @return the first header value; or {@code null} |
||||
*/ |
||||
@Override |
||||
public String getFirst(String headerName) { |
||||
return this.headers.getFirst(headerName); |
||||
} |
||||
|
||||
/** |
||||
* Add the given, single header value under the given name. |
||||
* @param headerName the header name |
||||
* @param headerValue the header value |
||||
* @throws UnsupportedOperationException if adding headers is not supported |
||||
* @see #put(String, List) |
||||
* @see #set(String, String) |
||||
*/ |
||||
@Override |
||||
public void add(String headerName, String headerValue) { |
||||
this.headers.add(headerName, headerValue); |
||||
} |
||||
|
||||
/** |
||||
* Set the given, single header value under the given name. |
||||
* @param headerName the header name |
||||
* @param headerValue the header value |
||||
* @throws UnsupportedOperationException if adding headers is not supported |
||||
* @see #put(String, List) |
||||
* @see #add(String, String) |
||||
*/ |
||||
@Override |
||||
public void set(String headerName, String headerValue) { |
||||
this.headers.set(headerName, headerValue); |
||||
} |
||||
|
||||
@Override |
||||
public void setAll(Map<String, String> values) { |
||||
this.headers.setAll(values); |
||||
} |
||||
|
||||
@Override |
||||
public Map<String, String> toSingleValueMap() { |
||||
return this.headers.toSingleValueMap(); |
||||
} |
||||
|
||||
// Map implementation
|
||||
|
||||
@Override |
||||
public int size() { |
||||
return this.headers.size(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isEmpty() { |
||||
return this.headers.isEmpty(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean containsKey(Object key) { |
||||
return this.headers.containsKey(key); |
||||
} |
||||
|
||||
@Override |
||||
public boolean containsValue(Object value) { |
||||
return this.headers.containsValue(value); |
||||
} |
||||
|
||||
@Override |
||||
public List<String> get(Object key) { |
||||
return this.headers.get(key); |
||||
} |
||||
|
||||
@Override |
||||
public List<String> put(String key, List<String> value) { |
||||
return this.headers.put(key, value); |
||||
} |
||||
|
||||
@Override |
||||
public List<String> remove(Object key) { |
||||
return this.headers.remove(key); |
||||
} |
||||
|
||||
@Override |
||||
public void putAll(Map<? extends String, ? extends List<String>> m) { |
||||
this.headers.putAll(m); |
||||
} |
||||
|
||||
@Override |
||||
public void clear() { |
||||
this.headers.clear(); |
||||
} |
||||
|
||||
@Override |
||||
public Set<String> keySet() { |
||||
return this.headers.keySet(); |
||||
} |
||||
|
||||
@Override |
||||
public Collection<List<String>> values() { |
||||
return this.headers.values(); |
||||
} |
||||
|
||||
@Override |
||||
public Set<Entry<String, List<String>>> entrySet() { |
||||
return this.headers.entrySet(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean equals(Object other) { |
||||
if (this == other) { |
||||
return true; |
||||
} |
||||
if (!(other instanceof WebSocketHttpHeaders)) { |
||||
return false; |
||||
} |
||||
WebSocketHttpHeaders otherHeaders = (WebSocketHttpHeaders) other; |
||||
return this.headers.equals(otherHeaders.headers); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return this.headers.hashCode(); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return this.headers.toString(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
/* |
||||
* Copyright 2002-2013 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.web.socket; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertThat; |
||||
|
||||
import org.hamcrest.Matchers; |
||||
import org.junit.Test; |
||||
import org.springframework.web.socket.support.WebSocketExtension; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Test fixture for {@link org.springframework.web.socket.support.WebSocketExtension} |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class WebSocketExtensionTests { |
||||
|
||||
@Test |
||||
public void parseHeaderSingle() { |
||||
List<WebSocketExtension> extensions = WebSocketExtension.parseExtensions("x-test-extension ; foo=bar ; bar=baz"); |
||||
assertThat(extensions, Matchers.hasSize(1)); |
||||
WebSocketExtension extension = extensions.get(0); |
||||
|
||||
assertEquals("x-test-extension", extension.getName()); |
||||
assertEquals(2, extension.getParameters().size()); |
||||
assertEquals("bar", extension.getParameters().get("foo")); |
||||
assertEquals("baz", extension.getParameters().get("bar")); |
||||
} |
||||
|
||||
@Test |
||||
public void parseHeaderMultiple() { |
||||
List<WebSocketExtension> extensions = WebSocketExtension.parseExtensions("x-foo-extension, x-bar-extension"); |
||||
assertThat(extensions, Matchers.hasSize(2)); |
||||
assertEquals("x-foo-extension", extensions.get(0).getName()); |
||||
assertEquals("x-bar-extension", extensions.get(1).getName()); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,173 @@
@@ -0,0 +1,173 @@
|
||||
/* |
||||
* Copyright 2002-2013 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.web.socket; |
||||
|
||||
|
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.junit.runners.Parameterized; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.http.server.ServerHttpRequest; |
||||
import org.springframework.http.server.ServerHttpResponse; |
||||
import org.springframework.web.socket.adapter.TextWebSocketHandlerAdapter; |
||||
import org.springframework.web.socket.adapter.WebSocketHandlerAdapter; |
||||
import org.springframework.web.socket.client.endpoint.StandardWebSocketClient; |
||||
import org.springframework.web.socket.client.jetty.JettyWebSocketClient; |
||||
import org.springframework.web.socket.server.DefaultHandshakeHandler; |
||||
import org.springframework.web.socket.server.HandshakeFailureException; |
||||
import org.springframework.web.socket.server.RequestUpgradeStrategy; |
||||
import org.springframework.web.socket.server.config.EnableWebSocket; |
||||
import org.springframework.web.socket.server.config.WebSocketConfigurer; |
||||
import org.springframework.web.socket.server.config.WebSocketHandlerRegistry; |
||||
import org.springframework.web.socket.support.WebSocketExtension; |
||||
import org.springframework.web.socket.support.WebSocketHttpHeaders; |
||||
|
||||
import java.net.URI; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.CopyOnWriteArrayList; |
||||
import java.util.concurrent.CountDownLatch; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
|
||||
/** |
||||
* Client and server-side WebSocket integration tests. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
@RunWith(Parameterized.class) |
||||
public class WebSocketIntegrationTests extends AbstractWebSocketIntegrationTests { |
||||
|
||||
@Parameterized.Parameters |
||||
public static Iterable<Object[]> arguments() { |
||||
return Arrays.asList(new Object[][]{ |
||||
{new JettyWebSocketTestServer(), new JettyWebSocketClient()}, |
||||
{new TomcatWebSocketTestServer(), new StandardWebSocketClient()} |
||||
}); |
||||
}; |
||||
|
||||
|
||||
@Override |
||||
protected Class<?>[] getAnnotatedConfigClasses() { |
||||
return new Class<?>[] { TestWebSocketConfigurer.class }; |
||||
} |
||||
|
||||
@Test |
||||
public void subProtocolNegotiation() throws Exception { |
||||
|
||||
WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); |
||||
headers.setSecWebSocketProtocol("foo"); |
||||
|
||||
WebSocketSession session = this.webSocketClient.doHandshake( |
||||
new WebSocketHandlerAdapter(), headers, new URI(getWsBaseUrl() + "/ws")).get(); |
||||
|
||||
assertEquals("foo", session.getAcceptedProtocol()); |
||||
} |
||||
|
||||
|
||||
@Configuration |
||||
@EnableWebSocket |
||||
static class TestWebSocketConfigurer implements WebSocketConfigurer { |
||||
|
||||
@Autowired |
||||
private DefaultHandshakeHandler handshakeHandler; // can't rely on classpath for server detection
|
||||
|
||||
@Override |
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { |
||||
this.handshakeHandler.setSupportedProtocols("foo", "bar", "baz"); |
||||
registry.addHandler(serverHandler(), "/ws").setHandshakeHandler(this.handshakeHandler); |
||||
} |
||||
|
||||
@Bean |
||||
public TestServerWebSocketHandler serverHandler() { |
||||
return new TestServerWebSocketHandler(); |
||||
} |
||||
} |
||||
|
||||
private static class TestClientWebSocketHandler extends TextWebSocketHandlerAdapter { |
||||
|
||||
private final TextMessage[] messagesToSend; |
||||
|
||||
private final int expected; |
||||
|
||||
private final List<TextMessage> actual = new CopyOnWriteArrayList<TextMessage>(); |
||||
|
||||
private final CountDownLatch latch; |
||||
|
||||
|
||||
public TestClientWebSocketHandler(int expectedNumberOfMessages, TextMessage... messagesToSend) { |
||||
this.messagesToSend = messagesToSend; |
||||
this.expected = expectedNumberOfMessages; |
||||
this.latch = new CountDownLatch(this.expected); |
||||
} |
||||
|
||||
@Override |
||||
public void afterConnectionEstablished(WebSocketSession session) throws Exception { |
||||
for (TextMessage message : this.messagesToSend) { |
||||
session.sendMessage(message); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { |
||||
this.actual.add(message); |
||||
this.latch.countDown(); |
||||
} |
||||
} |
||||
|
||||
private static class TestServerWebSocketHandler extends TextWebSocketHandlerAdapter { |
||||
|
||||
} |
||||
|
||||
private static class TestRequestUpgradeStrategy implements RequestUpgradeStrategy { |
||||
|
||||
private final RequestUpgradeStrategy delegate; |
||||
|
||||
private List<WebSocketExtension> extensions= new ArrayList<WebSocketExtension>(); |
||||
|
||||
|
||||
private TestRequestUpgradeStrategy(RequestUpgradeStrategy delegate, String... supportedExtensions) { |
||||
this.delegate = delegate; |
||||
for (String name : supportedExtensions) { |
||||
this.extensions.add(new WebSocketExtension(name)); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String[] getSupportedVersions() { |
||||
return this.delegate.getSupportedVersions(); |
||||
} |
||||
|
||||
@Override |
||||
public List<WebSocketExtension> getSupportedExtensions(ServerHttpRequest request) { |
||||
return this.extensions; |
||||
} |
||||
|
||||
@Override |
||||
public void upgrade(ServerHttpRequest request, ServerHttpResponse response, |
||||
String selectedProtocol, List<WebSocketExtension> selectedExtensions, |
||||
WebSocketHandler wsHandler, Map<String, Object> attributes) throws HandshakeFailureException { |
||||
|
||||
this.delegate.upgrade(request, response, selectedProtocol, selectedExtensions, wsHandler, attributes); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
/* |
||||
* Copyright 2002-2013 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.web.socket.support; |
||||
|
||||
import org.hamcrest.Matchers; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.springframework.http.HttpHeaders; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import static org.junit.Assert.assertThat; |
||||
|
||||
/** |
||||
* Unit tests for WebSocketHttpHeaders. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class WebSocketHttpHeadersTests { |
||||
|
||||
private WebSocketHttpHeaders headers; |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
headers = new WebSocketHttpHeaders(); |
||||
} |
||||
|
||||
@Test |
||||
public void parseWebSocketExtensions() { |
||||
List<String> extensions = new ArrayList<String>(); |
||||
extensions.add("x-foo-extension, x-bar-extension"); |
||||
extensions.add("x-test-extension"); |
||||
this.headers.put(WebSocketHttpHeaders.SEC_WEBSOCKET_EXTENSIONS, extensions); |
||||
|
||||
List<WebSocketExtension> parsedExtensions = this.headers.getSecWebSocketExtensions(); |
||||
assertThat(parsedExtensions, Matchers.hasSize(3)); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue