Update WebSocket extensions change

- add WebSocketHttpHeaders
- client-side support for WebSocket extensions
- DefaultHandshakeHandler updates
- replace use of ServletAttributes in JettyRequestUpgradeStratey
- upgrade spring-web to jetty 9.0.5
This commit is contained in:
Rossen Stoyanchev
2013-10-28 22:37:01 -04:00
parent 6d00a3f0ee
commit 81dda069af
34 changed files with 1112 additions and 596 deletions

View File

@@ -117,7 +117,7 @@ public abstract class AbstractWebSocketIntegrationTests {
static abstract class AbstractRequestUpgradeStrategyConfig {
@Bean
public HandshakeHandler handshakeHandler() {
public DefaultHandshakeHandler handshakeHandler() {
return new DefaultHandshakeHandler(requestUpgradeStrategy());
}

View File

@@ -21,41 +21,35 @@ 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 WebSocketExtension}
* Test fixture for {@link org.springframework.web.socket.support.WebSocketExtension}
* @author Brian Clozel
*/
public class WebSocketExtensionTests {
@Test
public void parseHeaderSingle() {
List<WebSocketExtension> extensions = WebSocketExtension.parseHeader("x-test-extension ; foo=bar");
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(1, extension.getParameters().size());
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.parseHeader("x-foo-extension, x-bar-extension");
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());
}
@Test
public void parseHeaders() {
List<String> extensions = new ArrayList<String>();
extensions.add("x-foo-extension, x-bar-extension");
extensions.add("x-test-extension");
List<WebSocketExtension> parsedExtensions = WebSocketExtension.parseHeaders(extensions);
assertThat(parsedExtensions, Matchers.hasSize(3));
}
}

View File

@@ -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);
}
}
}

View File

@@ -31,6 +31,7 @@ import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.adapter.WebSocketHandlerAdapter;
import org.springframework.web.socket.support.LoggingWebSocketHandlerDecorator;
import org.springframework.web.socket.support.WebSocketHandlerDecorator;
import org.springframework.web.socket.support.WebSocketHttpHeaders;
import org.springframework.web.util.UriComponentsBuilder;
import static org.junit.Assert.*;
@@ -54,7 +55,7 @@ public class WebSocketConnectionManagerTests {
manager.setSubProtocols(subprotocols);
manager.openConnection();
HttpHeaders expectedHeaders = new HttpHeaders();
WebSocketHttpHeaders expectedHeaders = new WebSocketHttpHeaders();
expectedHeaders.setSecWebSocketProtocol(subprotocols);
assertEquals(expectedHeaders, client.headers);
@@ -150,7 +151,7 @@ public class WebSocketConnectionManagerTests {
@Override
public ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler,
HttpHeaders headers, URI uri) {
WebSocketHttpHeaders headers, URI uri) {
this.webSocketHandler = webSocketHandler;
this.headers = headers;

View File

@@ -34,6 +34,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.adapter.WebSocketHandlerAdapter;
import org.springframework.web.socket.support.WebSocketHttpHeaders;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
@@ -51,12 +52,12 @@ public class StandardWebSocketClientTests {
private WebSocketHandler wsHandler;
private HttpHeaders headers;
private WebSocketHttpHeaders headers;
@Before
public void setup() {
this.headers = new HttpHeaders();
this.headers = new WebSocketHttpHeaders();
this.wsHandler = new WebSocketHandlerAdapter();
this.wsContainer = mock(WebSocketContainer.class);
this.wsClient = new StandardWebSocketClient(this.wsContainer);

View File

@@ -36,6 +36,7 @@ import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.adapter.JettyWebSocketHandlerAdapter;
import org.springframework.web.socket.adapter.JettyWebSocketSession;
import org.springframework.web.socket.adapter.TextWebSocketHandlerAdapter;
import org.springframework.web.socket.support.WebSocketHttpHeaders;
import static org.junit.Assert.*;
@@ -80,7 +81,7 @@ public class JettyWebSocketClientTests {
@Test
public void doHandshake() throws Exception {
HttpHeaders headers = new HttpHeaders();
WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
headers.setSecWebSocketProtocol(Arrays.asList("echo"));
this.wsSession = this.client.doHandshake(new TextWebSocketHandlerAdapter(), headers, new URI(this.wsUrl)).get();

View File

@@ -24,8 +24,10 @@ import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.web.socket.AbstractHttpRequestTests;
import org.springframework.web.socket.support.WebSocketExtension;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.adapter.TextWebSocketHandlerAdapter;
import org.springframework.web.socket.support.WebSocketHttpHeaders;
import static org.mockito.Mockito.*;
@@ -57,19 +59,22 @@ public class DefaultHandshakeHandlerTests extends AbstractHttpRequestTests {
when(this.upgradeStrategy.getSupportedVersions()).thenReturn(new String[] { "13" });
WebSocketHttpHeaders headers = new WebSocketHttpHeaders(this.request.getHeaders());
this.servletRequest.setMethod("GET");
this.request.getHeaders().setUpgrade("WebSocket");
this.request.getHeaders().setConnection("Upgrade");
this.request.getHeaders().setSecWebSocketVersion("13");
this.request.getHeaders().setSecWebSocketKey("82/ZS2YHjEnUN97HLL8tbw==");
this.request.getHeaders().setSecWebSocketProtocol("STOMP");
headers.setUpgrade("WebSocket");
headers.setConnection("Upgrade");
headers.setSecWebSocketVersion("13");
headers.setSecWebSocketKey("82/ZS2YHjEnUN97HLL8tbw==");
headers.setSecWebSocketProtocol("STOMP");
WebSocketHandler handler = new TextWebSocketHandlerAdapter();
Map<String, Object> attributes = Collections.<String, Object>emptyMap();
this.handshakeHandler.doHandshake(this.request, this.response, handler, attributes);
verify(this.upgradeStrategy).upgrade(this.request, this.response, "STOMP", handler, attributes);
verify(this.upgradeStrategy).upgrade(this.request, this.response,
"STOMP", Collections.<WebSocketExtension>emptyList(), handler, attributes);
}
}

View File

@@ -41,7 +41,7 @@ import static org.junit.Assert.*;
/**
* Test fixture for WebSocket Java config support.
* Integration tests for WebSocket Java server-side configuration.
*
* @author Rossen Stoyanchev
*/

View File

@@ -26,7 +26,7 @@ import java.util.Map;
import org.springframework.http.HttpHeaders;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketExtension;
import org.springframework.web.socket.support.WebSocketExtension;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
@@ -59,7 +59,7 @@ public class TestSockJsSession extends AbstractSockJsSession {
private String subProtocol;
private List<WebSocketExtension> extensions = new ArrayList<WebSocketExtension>();
private List<WebSocketExtension> extensions = new ArrayList<>();
public TestSockJsSession(String sessionId, SockJsServiceConfig config,
@@ -83,61 +83,37 @@ public class TestSockJsSession extends AbstractSockJsSession {
return this.headers;
}
/**
* @return the headers
*/
public HttpHeaders getHeaders() {
return this.headers;
}
/**
* @param headers the headers to set
*/
public void setHeaders(HttpHeaders headers) {
this.headers = headers;
}
/**
* @return the principal
*/
@Override
public Principal getPrincipal() {
return this.principal;
}
/**
* @param principal the principal to set
*/
public void setPrincipal(Principal principal) {
this.principal = principal;
}
/**
* @return the localAddress
*/
@Override
public InetSocketAddress getLocalAddress() {
return this.localAddress;
}
/**
* @param localAddress the remoteAddress to set
*/
public void setLocalAddress(InetSocketAddress localAddress) {
this.localAddress = localAddress;
}
/**
* @return the remoteAddress
*/
@Override
public InetSocketAddress getRemoteAddress() {
return this.remoteAddress;
}
/**
* @param remoteAddress the remoteAddress to set
*/
public void setRemoteAddress(InetSocketAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
@@ -151,17 +127,14 @@ public class TestSockJsSession extends AbstractSockJsSession {
this.subProtocol = protocol;
}
/**
* @return the extensions
*/
@Override
public List<WebSocketExtension> getExtensions() { return this.extensions; }
public List<WebSocketExtension> getExtensions() {
return this.extensions;
}
/**
*
* @param extensions the extensions to set
*/
public void setExtensions(List<WebSocketExtension> extensions) { this.extensions = extensions; }
public void setExtensions(List<WebSocketExtension> extensions) {
this.extensions = extensions;
}
public CloseStatus getCloseStatus() {
return this.closeStatus;

View File

@@ -27,7 +27,6 @@ import java.util.Map;
import org.springframework.http.HttpHeaders;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketExtension;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
@@ -63,32 +62,20 @@ public class TestWebSocketSession implements WebSocketSession {
private HttpHeaders headers;
/**
* @return the id
*/
@Override
public String getId() {
return this.id;
}
/**
* @param id the id to set
*/
public void setId(String id) {
this.id = id;
}
/**
* @return the uri
*/
@Override
public URI getUri() {
return this.uri;
}
/**
* @param uri the uri to set
*/
public void setUri(URI uri) {
this.uri = uri;
}
@@ -99,117 +86,72 @@ public class TestWebSocketSession implements WebSocketSession {
return this.headers;
}
/**
* @return the headers
*/
public HttpHeaders getHeaders() {
return this.headers;
}
/**
* @param headers the headers to set
*/
public void setHeaders(HttpHeaders headers) {
this.headers = headers;
}
/**
* @param attributes the attributes to set
*/
public void setHandshakeAttributes(Map<String, Object> attributes) {
this.attributes = attributes;
}
/**
* @return the attributes
*/
@Override
public Map<String, Object> getHandshakeAttributes() {
return this.attributes;
}
/**
* @return the principal
*/
@Override
public Principal getPrincipal() {
return this.principal;
}
/**
* @param principal the principal to set
*/
public void setPrincipal(Principal principal) {
this.principal = principal;
}
/**
* @return the localAddress
*/
@Override
public InetSocketAddress getLocalAddress() {
return this.localAddress;
}
/**
* @param localAddress the remoteAddress to set
*/
public void setLocalAddress(InetSocketAddress localAddress) {
this.localAddress = localAddress;
}
/**
* @return the remoteAddress
*/
@Override
public InetSocketAddress getRemoteAddress() {
return this.remoteAddress;
}
/**
* @param remoteAddress the remoteAddress to set
*/
public void setRemoteAddress(InetSocketAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
/**
* @return the subProtocol
*/
public String getAcceptedProtocol() {
return this.protocol;
}
/**
* @param protocol the subProtocol to set
*/
public void setAcceptedProtocol(String protocol) {
this.protocol = protocol;
}
/**
* @return the extensions
*/
@Override
public List<WebSocketExtension> getExtensions() { return this.extensions; }
public List<WebSocketExtension> getExtensions() {
return this.extensions;
}
/**
*
* @param extensions the extensions to set
*/
public void setExtensions(List<WebSocketExtension> extensions) { this.extensions = extensions; }
public void setExtensions(List<WebSocketExtension> extensions) {
this.extensions = extensions;
}
/**
* @return the open
*/
@Override
public boolean isOpen() {
return this.open;
}
/**
* @param open the open to set
*/
public void setOpen(boolean open) {
this.open = open;
}

View File

@@ -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));
}
}