init
This commit is contained in:
151
test/org/apache/tomcat/websocket/server/TestClassLoader.java
Normal file
151
test/org/apache/tomcat/websocket/server/TestClassLoader.java
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.websocket.ClientEndpoint;
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.OnMessage;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.loader.WebappClassLoaderBase;
|
||||
import org.apache.catalina.servlets.DefaultServlet;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.tomcat.websocket.WebSocketBaseTest;
|
||||
|
||||
/**
|
||||
* Tests endpoint methods are called with the correct class loader.
|
||||
*/
|
||||
public class TestClassLoader extends WebSocketBaseTest {
|
||||
|
||||
private static final String PASS = "PASS";
|
||||
private static final String FAIL = "FAIL";
|
||||
|
||||
|
||||
/*
|
||||
* Checks class loader for the server endpoint during onOpen and onMessage
|
||||
*/
|
||||
@Test
|
||||
public void testSimple() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
ctx.addApplicationListener(Config.class.getName());
|
||||
|
||||
Tomcat.addServlet(ctx, "default", new DefaultServlet());
|
||||
ctx.addServletMappingDecoded("/", "default");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
WebSocketContainer wsContainer = ContainerProvider.getWebSocketContainer();
|
||||
|
||||
Client client = new Client();
|
||||
|
||||
Session wsSession = wsContainer.connectToServer(client,
|
||||
new URI("ws://localhost:" + getPort() + "/test"));
|
||||
|
||||
Assert.assertTrue(wsSession.isOpen());
|
||||
|
||||
// Wait up to 5s for a message
|
||||
int count = 0;
|
||||
while (count < 50 && client.getMsgCount() < 1) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
// Check it
|
||||
Assert.assertEquals(1, client.getMsgCount());
|
||||
Assert.assertFalse(client.hasFailed());
|
||||
|
||||
wsSession.getBasicRemote().sendText("Testing");
|
||||
|
||||
// Wait up to 5s for a message
|
||||
count = 0;
|
||||
while (count < 50 && client.getMsgCount() < 2) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
Assert.assertEquals(2, client.getMsgCount());
|
||||
Assert.assertFalse(client.hasFailed());
|
||||
|
||||
wsSession.close();
|
||||
}
|
||||
|
||||
@ClientEndpoint
|
||||
public static class Client {
|
||||
|
||||
private final AtomicInteger msgCount = new AtomicInteger(0);
|
||||
private boolean failed = false;
|
||||
|
||||
public boolean hasFailed() {
|
||||
return failed;
|
||||
}
|
||||
|
||||
public int getMsgCount() {
|
||||
return msgCount.get();
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(String msg) {
|
||||
if (!failed && !PASS.equals(msg)) {
|
||||
failed = true;
|
||||
}
|
||||
msgCount.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ServerEndpoint("/test")
|
||||
public static class ClassLoaderEndpoint {
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session) throws IOException {
|
||||
if (Thread.currentThread().getContextClassLoader() instanceof WebappClassLoaderBase) {
|
||||
session.getBasicRemote().sendText(PASS);
|
||||
} else {
|
||||
session.getBasicRemote().sendText(FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public String onMessage(@SuppressWarnings("unused") String msg) {
|
||||
if (Thread.currentThread().getContextClassLoader() instanceof WebappClassLoaderBase) {
|
||||
return PASS;
|
||||
} else {
|
||||
return FAIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Config extends TesterEndpointConfig {
|
||||
|
||||
@Override
|
||||
protected Class<?> getEndpointClass() {
|
||||
return ClassLoaderEndpoint.class;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
339
test/org/apache/tomcat/websocket/server/TestClose.java
Normal file
339
test/org/apache/tomcat/websocket/server/TestClose.java
Normal file
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.CloseReason.CloseCode;
|
||||
import javax.websocket.CloseReason.CloseCodes;
|
||||
import javax.websocket.OnClose;
|
||||
import javax.websocket.OnError;
|
||||
import javax.websocket.OnMessage;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.websocket.WebSocketBaseTest;
|
||||
|
||||
/**
|
||||
* Test the behavior of closing websockets under various conditions.
|
||||
*/
|
||||
public class TestClose extends WebSocketBaseTest {
|
||||
|
||||
private static Log log = LogFactory.getLog(TestClose.class);
|
||||
|
||||
// These are static because it is simpler than trying to inject them into
|
||||
// the endpoint
|
||||
private static volatile Events events;
|
||||
|
||||
|
||||
public static class Events {
|
||||
// Used to block in the @OnMessage
|
||||
public final CountDownLatch onMessageWait = new CountDownLatch(1);
|
||||
|
||||
// Used to check which methods of a server endpoint were called
|
||||
public final CountDownLatch onErrorCalled = new CountDownLatch(1);
|
||||
public final CountDownLatch onMessageCalled = new CountDownLatch(1);
|
||||
public final CountDownLatch onCloseCalled = new CountDownLatch(1);
|
||||
|
||||
// Parameter of an @OnClose call
|
||||
public volatile CloseReason closeReason = null;
|
||||
// Parameter of an @OnError call
|
||||
public volatile Throwable onErrorThrowable = null;
|
||||
|
||||
//This is set to true for tests where the @OnMessage should send a message
|
||||
public volatile boolean onMessageSends = false;
|
||||
}
|
||||
|
||||
|
||||
private static void awaitLatch(CountDownLatch latch, String failMessage) {
|
||||
try {
|
||||
if (!latch.await(5000, TimeUnit.MILLISECONDS)) {
|
||||
Assert.fail(failMessage);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Won't happen
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void awaitOnClose(CloseCode... codes) {
|
||||
Set<CloseCode> set = new HashSet<>();
|
||||
for (CloseCode code : codes) {
|
||||
set.add(code);
|
||||
}
|
||||
awaitOnClose(set);
|
||||
}
|
||||
|
||||
|
||||
public static void awaitOnClose(Set<CloseCode> codes) {
|
||||
awaitLatch(events.onCloseCalled, "onClose not called");
|
||||
CloseCode received = events.closeReason.getCloseCode();
|
||||
Assert.assertTrue("Rx: " + received, codes.contains(received));
|
||||
}
|
||||
|
||||
|
||||
public static void awaitOnError(Class<? extends Throwable> exceptionClazz) {
|
||||
awaitLatch(events.onErrorCalled, "onError not called");
|
||||
Assert.assertTrue(events.onErrorThrowable.getClass().getName(),
|
||||
exceptionClazz.isAssignableFrom(events.onErrorThrowable.getClass()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
events = new Events();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTcpClose() throws Exception {
|
||||
// TODO
|
||||
Assume.assumeFalse("This test currently fails for APR",
|
||||
getTomcatInstance().getConnector().getProtocolHandlerClassName().contains("Apr"));
|
||||
|
||||
startServer(TestEndpointConfig.class);
|
||||
|
||||
TesterWsClient client = new TesterWsClient("localhost", getPort());
|
||||
client.httpUpgrade(BaseEndpointConfig.PATH);
|
||||
client.closeSocket();
|
||||
|
||||
awaitOnClose(CloseCodes.CLOSED_ABNORMALLY);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTcpReset() throws Exception {
|
||||
startServer(TestEndpointConfig.class);
|
||||
|
||||
TesterWsClient client = new TesterWsClient("localhost", getPort());
|
||||
client.httpUpgrade(BaseEndpointConfig.PATH);
|
||||
client.forceCloseSocket();
|
||||
|
||||
// TODO: I'm not entirely sure when onError should be called
|
||||
awaitOnError(IOException.class);
|
||||
awaitOnClose(CloseCodes.CLOSED_ABNORMALLY);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWsCloseThenTcpClose() throws Exception {
|
||||
startServer(TestEndpointConfig.class);
|
||||
|
||||
TesterWsClient client = new TesterWsClient("localhost", getPort());
|
||||
client.httpUpgrade(BaseEndpointConfig.PATH);
|
||||
client.sendCloseFrame(CloseCodes.GOING_AWAY);
|
||||
client.closeSocket();
|
||||
|
||||
awaitOnClose(CloseCodes.GOING_AWAY);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWsCloseThenTcpReset() throws Exception {
|
||||
startServer(TestEndpointConfig.class);
|
||||
|
||||
TesterWsClient client = new TesterWsClient("localhost", getPort());
|
||||
client.httpUpgrade(BaseEndpointConfig.PATH);
|
||||
client.sendCloseFrame(CloseCodes.GOING_AWAY);
|
||||
client.forceCloseSocket();
|
||||
|
||||
// WebSocket 1.1, section 2.1.5 requires this to be CLOSED_ABNORMALLY if
|
||||
// the container initiates the close and the close code from the client
|
||||
// if the client initiates it. When the client resets the TCP connection
|
||||
// after sending the close, different operating systems react different
|
||||
// ways. Some present the close message then drop the connection, some
|
||||
// just drop the connection. Therefore, this test has to handle both
|
||||
// close codes.
|
||||
awaitOnClose(CloseCodes.CLOSED_ABNORMALLY, CloseCodes.GOING_AWAY);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTcpCloseInOnMessage() throws Exception {
|
||||
// TODO
|
||||
Assume.assumeFalse("This test currently fails for APR",
|
||||
getTomcatInstance().getConnector().getProtocolHandlerClassName().contains("Apr"));
|
||||
|
||||
startServer(TestEndpointConfig.class);
|
||||
|
||||
TesterWsClient client = new TesterWsClient("localhost", getPort());
|
||||
client.httpUpgrade(BaseEndpointConfig.PATH);
|
||||
client.sendTextMessage("Test");
|
||||
awaitLatch(events.onMessageCalled, "onMessage not called");
|
||||
|
||||
client.closeSocket();
|
||||
events.onMessageWait.countDown();
|
||||
|
||||
awaitOnClose(CloseCodes.CLOSED_ABNORMALLY);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTcpResetInOnMessage() throws Exception {
|
||||
startServer(TestEndpointConfig.class);
|
||||
|
||||
TesterWsClient client = new TesterWsClient("localhost", getPort());
|
||||
client.httpUpgrade(BaseEndpointConfig.PATH);
|
||||
client.sendTextMessage("Test");
|
||||
awaitLatch(events.onMessageCalled, "onMessage not called");
|
||||
|
||||
client.forceCloseSocket();
|
||||
events.onMessageWait.countDown();
|
||||
|
||||
awaitOnError(IOException.class);
|
||||
awaitOnClose(CloseCodes.CLOSED_ABNORMALLY);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTcpCloseWhenOnMessageSends() throws Exception {
|
||||
events.onMessageSends = true;
|
||||
testTcpCloseInOnMessage();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTcpResetWhenOnMessageSends() throws Exception {
|
||||
events.onMessageSends = true;
|
||||
testTcpResetInOnMessage();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWsCloseThenTcpCloseWhenOnMessageSends() throws Exception {
|
||||
events.onMessageSends = true;
|
||||
|
||||
startServer(TestEndpointConfig.class);
|
||||
|
||||
TesterWsClient client = new TesterWsClient("localhost", getPort());
|
||||
client.httpUpgrade(BaseEndpointConfig.PATH);
|
||||
client.sendTextMessage("Test");
|
||||
awaitLatch(events.onMessageCalled, "onMessage not called");
|
||||
|
||||
client.sendCloseFrame(CloseCodes.NORMAL_CLOSURE);
|
||||
client.closeSocket();
|
||||
events.onMessageWait.countDown();
|
||||
|
||||
awaitOnClose(CloseCodes.CLOSED_ABNORMALLY);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWsCloseThenTcpResetWhenOnMessageSends() throws Exception {
|
||||
events.onMessageSends = true;
|
||||
|
||||
startServer(TestEndpointConfig.class);
|
||||
|
||||
TesterWsClient client = new TesterWsClient("localhost", getPort());
|
||||
client.httpUpgrade(BaseEndpointConfig.PATH);
|
||||
client.sendTextMessage("Test");
|
||||
awaitLatch(events.onMessageCalled, "onMessage not called");
|
||||
|
||||
client.sendCloseFrame(CloseCodes.NORMAL_CLOSURE);
|
||||
client.forceCloseSocket();
|
||||
events.onMessageWait.countDown();
|
||||
|
||||
awaitOnClose(CloseCodes.CLOSED_ABNORMALLY);
|
||||
}
|
||||
|
||||
|
||||
public static class TestEndpoint {
|
||||
|
||||
@OnOpen
|
||||
public void onOpen() {
|
||||
log.info("Session opened");
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(Session session, String message) {
|
||||
log.info("Message received: " + message);
|
||||
events.onMessageCalled.countDown();
|
||||
awaitLatch(events.onMessageWait, "onMessageWait not triggered");
|
||||
|
||||
if (events.onMessageSends) {
|
||||
try {
|
||||
int count = 0;
|
||||
// The latches above are meant to ensure the correct
|
||||
// sequence of events but in some cases, particularly with
|
||||
// APR, there is a short delay between the client closing /
|
||||
// resetting the connection and the server recognising that
|
||||
// fact. This loop tries to ensure that it lasts much longer
|
||||
// than that delay so any close / reset from the client
|
||||
// triggers an error here.
|
||||
while (count < 10) {
|
||||
count++;
|
||||
session.getBasicRemote().sendText("Test reply");
|
||||
Thread.sleep(500);
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
// Expected to fail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Throwable t) {
|
||||
log.info("onError", t);
|
||||
events.onErrorThrowable = t;
|
||||
events.onErrorCalled.countDown();
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(CloseReason cr) {
|
||||
log.info("onClose: " + cr);
|
||||
events.closeReason = cr;
|
||||
events.onCloseCalled.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class TestEndpointConfig extends BaseEndpointConfig {
|
||||
|
||||
@Override
|
||||
protected Class<?> getEndpointClass() {
|
||||
return TestEndpoint.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public abstract static class BaseEndpointConfig extends TesterEndpointConfig {
|
||||
|
||||
public static final String PATH = "/test";
|
||||
|
||||
@Override
|
||||
protected ServerEndpointConfig getServerEndpointConfig() {
|
||||
return ServerEndpointConfig.Builder.create(getEndpointClass(), PATH).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
168
test/org/apache/tomcat/websocket/server/TestCloseBug58624.java
Normal file
168
test/org/apache/tomcat/websocket/server/TestCloseBug58624.java
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.apache.tomcat.websocket.server;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.websocket.ClientEndpoint;
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.OnClose;
|
||||
import javax.websocket.OnError;
|
||||
import javax.websocket.OnMessage;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
import javax.websocket.server.ServerContainer;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.servlets.DefaultServlet;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.tomcat.websocket.WebSocketBaseTest;
|
||||
|
||||
public class TestCloseBug58624 extends WebSocketBaseTest {
|
||||
|
||||
@Test
|
||||
public void testOnErrorNotCalledWhenClosingConnection() throws Throwable {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
ctx.addApplicationListener(Bug58624ServerConfig.class.getName());
|
||||
Tomcat.addServlet(ctx, "default", new DefaultServlet());
|
||||
ctx.addServletMappingDecoded("/", "default");
|
||||
|
||||
WebSocketContainer wsContainer = ContainerProvider.getWebSocketContainer();
|
||||
|
||||
tomcat.start();
|
||||
|
||||
Bug58624ClientEndpoint client = new Bug58624ClientEndpoint();
|
||||
URI uri = new URI("ws://localhost:" + getPort() + Bug58624ServerConfig.PATH);
|
||||
|
||||
Session session = wsContainer.connectToServer(client, uri);
|
||||
|
||||
// Wait for session to open on the server
|
||||
int count = 0;
|
||||
while (count < 50 && Bug58624ServerEndpoint.getOpenSessionCount() == 0) {
|
||||
count++;
|
||||
Thread.sleep(100);
|
||||
}
|
||||
Assert.assertNotEquals(0, Bug58624ServerEndpoint.getOpenSessionCount());
|
||||
|
||||
// Now close the session
|
||||
session.close();
|
||||
|
||||
// Wait for session to close on the server
|
||||
count = 0;
|
||||
while (count < 50 && Bug58624ServerEndpoint.getOpenSessionCount() > 0) {
|
||||
count++;
|
||||
Thread.sleep(100);
|
||||
}
|
||||
Assert.assertEquals(0, Bug58624ServerEndpoint.getOpenSessionCount());
|
||||
|
||||
// Ensure no errors were reported on the server
|
||||
Assert.assertEquals(0, Bug58624ServerEndpoint.getErrorCount());
|
||||
|
||||
if (client.getError() != null) {
|
||||
throw client.getError();
|
||||
}
|
||||
}
|
||||
|
||||
@ClientEndpoint
|
||||
public class Bug58624ClientEndpoint {
|
||||
|
||||
private volatile Throwable t;
|
||||
|
||||
|
||||
@OnError
|
||||
public void onError(Throwable t) {
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
|
||||
public Throwable getError() {
|
||||
return this.t;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Bug58624ServerConfig extends WsContextListener {
|
||||
|
||||
public static final String PATH = "/bug58624";
|
||||
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
super.contextInitialized(sce);
|
||||
|
||||
ServerContainer sc = (ServerContainer) sce.getServletContext().getAttribute(
|
||||
Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
|
||||
|
||||
ServerEndpointConfig sec = ServerEndpointConfig.Builder.create(
|
||||
Bug58624ServerEndpoint.class, PATH).build();
|
||||
|
||||
try {
|
||||
sc.addEndpoint(sec);
|
||||
} catch (DeploymentException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Bug58624ServerEndpoint {
|
||||
|
||||
private static AtomicInteger openSessionCount = new AtomicInteger(0);
|
||||
private static AtomicInteger errorCount = new AtomicInteger(0);
|
||||
|
||||
public static int getOpenSessionCount() {
|
||||
return openSessionCount.get();
|
||||
}
|
||||
|
||||
public static int getErrorCount() {
|
||||
return errorCount.get();
|
||||
}
|
||||
|
||||
@OnOpen
|
||||
public void onOpen() {
|
||||
openSessionCount.incrementAndGet();
|
||||
}
|
||||
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(@SuppressWarnings("unused") Session session, String message) {
|
||||
System.out.println("Received message " + message);
|
||||
}
|
||||
|
||||
|
||||
@OnError
|
||||
public void onError(Throwable t) {
|
||||
errorCount.incrementAndGet();
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
@OnClose
|
||||
public void onClose(@SuppressWarnings("unused") CloseReason cr) {
|
||||
openSessionCount.decrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
||||
111
test/org/apache/tomcat/websocket/server/TestShutdown.java
Normal file
111
test/org/apache/tomcat/websocket/server/TestShutdown.java
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.websocket.ClientEndpointConfig;
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.OnMessage;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.servlets.DefaultServlet;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.tomcat.websocket.TesterMessageCountClient.BasicText;
|
||||
import org.apache.tomcat.websocket.TesterMessageCountClient.TesterProgrammaticEndpoint;
|
||||
import org.apache.tomcat.websocket.WebSocketBaseTest;
|
||||
|
||||
/**
|
||||
* Tests inspired by https://bz.apache.org/bugzilla/show_bug.cgi?id=58835 to
|
||||
* check that WebSocket connections are closed gracefully on Tomcat shutdown.
|
||||
*/
|
||||
public class TestShutdown extends WebSocketBaseTest {
|
||||
|
||||
@Test
|
||||
public void testShutdownBufferedMessages() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
ctx.addApplicationListener(EchoBufferedConfig.class.getName());
|
||||
Tomcat.addServlet(ctx, "default", new DefaultServlet());
|
||||
ctx.addServletMappingDecoded("/", "default");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
WebSocketContainer wsContainer = ContainerProvider.getWebSocketContainer();
|
||||
ClientEndpointConfig clientEndpointConfig = ClientEndpointConfig.Builder.create().build();
|
||||
Session wsSession = wsContainer.connectToServer(
|
||||
TesterProgrammaticEndpoint.class,
|
||||
clientEndpointConfig,
|
||||
new URI("ws://localhost:" + getPort() + "/test"));
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
BasicText handler = new BasicText(latch);
|
||||
wsSession.addMessageHandler(handler);
|
||||
wsSession.getBasicRemote().sendText("Hello");
|
||||
|
||||
int count = 0;
|
||||
while (count < 10 && EchoBufferedEndpoint.messageCount.get() == 0) {
|
||||
Thread.sleep(200);
|
||||
count++;
|
||||
}
|
||||
Assert.assertNotEquals("Message not received by server",
|
||||
EchoBufferedEndpoint.messageCount.get(), 0);
|
||||
|
||||
tomcat.stop();
|
||||
|
||||
Assert.assertTrue("Latch expired waiting for message", latch.await(10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
public static class EchoBufferedConfig extends TesterEndpointConfig {
|
||||
|
||||
@Override
|
||||
protected Class<?> getEndpointClass() {
|
||||
return EchoBufferedEndpoint.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ServerEndpoint("/test")
|
||||
public static class EchoBufferedEndpoint {
|
||||
|
||||
private static AtomicLong messageCount = new AtomicLong(0);
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session, @SuppressWarnings("unused") EndpointConfig epc)
|
||||
throws IOException {
|
||||
session.getAsyncRemote().setBatchingAllowed(true);
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(Session session, String msg) throws IOException {
|
||||
messageCount.incrementAndGet();
|
||||
session.getBasicRemote().sendText(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
213
test/org/apache/tomcat/websocket/server/TestUriTemplate.java
Normal file
213
test/org/apache/tomcat/websocket/server/TestUriTemplate.java
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.apache.tomcat.websocket.server;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestUriTemplate {
|
||||
|
||||
@Test
|
||||
public void testBasic() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/{a}/{b}");
|
||||
Map<String,String> result = t.match(new UriTemplate("/foo/bar"));
|
||||
|
||||
Assert.assertEquals(2, result.size());
|
||||
Assert.assertTrue(result.containsKey("a"));
|
||||
Assert.assertTrue(result.containsKey("b"));
|
||||
Assert.assertEquals("foo", result.get("a"));
|
||||
Assert.assertEquals("bar", result.get("b"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOneOfTwo() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/{a}/{b}");
|
||||
Map<String,String> result = t.match(new UriTemplate("/foo"));
|
||||
Assert.assertNull(result);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=java.lang.IllegalArgumentException.class)
|
||||
public void testBasicPrefix() throws Exception {
|
||||
@SuppressWarnings("unused")
|
||||
UriTemplate t = new UriTemplate("/x{a}/y{b}");
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=java.lang.IllegalArgumentException.class)
|
||||
public void testPrefixOneOfTwo() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/x{a}/y{b}");
|
||||
t.match(new UriTemplate("/xfoo"));
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=java.lang.IllegalArgumentException.class)
|
||||
public void testPrefixTwoOfTwo() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/x{a}/y{b}");
|
||||
t.match(new UriTemplate("/ybar"));
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=java.lang.IllegalArgumentException.class)
|
||||
public void testQuote1() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/.{a}");
|
||||
t.match(new UriTemplate("/yfoo"));
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=java.lang.IllegalArgumentException.class)
|
||||
public void testQuote2() throws Exception {
|
||||
@SuppressWarnings("unused")
|
||||
UriTemplate t = new UriTemplate("/.{a}");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNoParams() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/foo/bar");
|
||||
Map<String,String> result = t.match(new UriTemplate("/foo/bar"));
|
||||
|
||||
Assert.assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSpecExample1_01() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/a/b");
|
||||
Map<String,String> result = t.match(new UriTemplate("/a/b"));
|
||||
|
||||
Assert.assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSpecExample1_02() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/a/b");
|
||||
Map<String,String> result = t.match(new UriTemplate("/a"));
|
||||
|
||||
Assert.assertNull(result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSpecExample1_03() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/a/b");
|
||||
Map<String,String> result = t.match(new UriTemplate("/a/bb"));
|
||||
|
||||
Assert.assertNull(result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSpecExample2_01() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/a/{var}");
|
||||
Map<String,String> result = t.match(new UriTemplate("/a/b"));
|
||||
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("b", result.get("var"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSpecExample2_02() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/a/{var}");
|
||||
Map<String,String> result = t.match(new UriTemplate("/a/apple"));
|
||||
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("apple", result.get("var"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSpecExample2_03() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/a/{var}");
|
||||
Map<String,String> result = t.match(new UriTemplate("/a"));
|
||||
|
||||
Assert.assertNull(result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSpecExample2_04() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/a/{var}");
|
||||
Map<String,String> result = t.match(new UriTemplate("/a/b/c"));
|
||||
|
||||
Assert.assertNull(result);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=java.lang.IllegalArgumentException.class)
|
||||
public void testDuplicate01() throws Exception {
|
||||
@SuppressWarnings("unused")
|
||||
UriTemplate t = new UriTemplate("/{var}/{var}");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDuplicate02() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/{a}/{b}");
|
||||
Map<String,String> result = t.match(new UriTemplate("/x/x"));
|
||||
|
||||
Assert.assertEquals(2, result.size());
|
||||
Assert.assertEquals("x", result.get("a"));
|
||||
Assert.assertEquals("x", result.get("b"));
|
||||
}
|
||||
|
||||
|
||||
public void testEgMailingList01() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/a/{var}");
|
||||
Map<String,String> result = t.match(new UriTemplate("/a/b/"));
|
||||
|
||||
Assert.assertNull(result);
|
||||
}
|
||||
|
||||
|
||||
public void testEgMailingList02() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/a/{var}");
|
||||
Map<String,String> result = t.match(new UriTemplate("/a/"));
|
||||
|
||||
Assert.assertNull(result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEgMailingList03() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/a/{var}");
|
||||
Map<String,String> result = t.match(new UriTemplate("/a"));
|
||||
|
||||
Assert.assertNull(result);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=java.lang.IllegalArgumentException.class)
|
||||
public void testEgMailingList04() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/a/{var1}/{var2}");
|
||||
@SuppressWarnings("unused")
|
||||
Map<String,String> result = t.match(new UriTemplate("/a//c"));
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=java.lang.IllegalArgumentException.class)
|
||||
public void testEgMailingList05() throws Exception {
|
||||
UriTemplate t = new UriTemplate("/a/{var}/");
|
||||
@SuppressWarnings("unused")
|
||||
Map<String,String> result = t.match(new UriTemplate("/a/b/"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.EncodeException;
|
||||
import javax.websocket.Encoder;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.OnClose;
|
||||
import javax.websocket.OnError;
|
||||
import javax.websocket.OnMessage;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.servlets.DefaultServlet;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.tomcat.websocket.WebSocketBaseTest;
|
||||
import org.apache.tomcat.websocket.pojo.TesterUtil.SimpleClient;
|
||||
|
||||
@Ignore // This test requires manual intervention to create breakpoints etc.
|
||||
public class TestWsRemoteEndpointImplServer extends WebSocketBaseTest {
|
||||
|
||||
/*
|
||||
* https://bz.apache.org/bugzilla/show_bug.cgi?id=58624
|
||||
*
|
||||
* This test requires three breakpoints to be set. Two in this file (marked
|
||||
* A & B with comments) and one (C) at the start of
|
||||
* WsRemoteEndpointImplServer.doWrite().
|
||||
*
|
||||
* With the breakpoints in place, run this test.
|
||||
* Once breakpoints A & B are reached, progress the thread at breakpoint A
|
||||
* one line to close the connection.
|
||||
* Once breakpoint C is reached, allow the thread at breakpoint B to
|
||||
* continue.
|
||||
* Then allow the thread at breakpoint C to continue.
|
||||
*
|
||||
* In the failure mode, the thread at breakpoint B will not progress past
|
||||
* the call to sendObject(). If the issue is fixed, the thread at breakpoint
|
||||
* B will continue past sendObject() and terminate with a TimeoutException.
|
||||
*/
|
||||
@Test
|
||||
public void testClientDropsConnection() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
ctx.addApplicationListener(Bug58624Config.class.getName());
|
||||
Tomcat.addServlet(ctx, "default", new DefaultServlet());
|
||||
ctx.addServletMappingDecoded("/", "default");
|
||||
|
||||
WebSocketContainer wsContainer =
|
||||
ContainerProvider.getWebSocketContainer();
|
||||
|
||||
tomcat.start();
|
||||
|
||||
SimpleClient client = new SimpleClient();
|
||||
URI uri = new URI("ws://localhost:" + getPort() + Bug58624Config.PATH);
|
||||
|
||||
Session session = wsContainer.connectToServer(client, uri);
|
||||
// Break point A required on following line
|
||||
session.close();
|
||||
}
|
||||
|
||||
public static class Bug58624Config extends TesterEndpointConfig {
|
||||
|
||||
public static final String PATH = "/bug58624";
|
||||
|
||||
|
||||
@Override
|
||||
protected ServerEndpointConfig getServerEndpointConfig() {
|
||||
List<Class<? extends Encoder>> encoders = new ArrayList<>();
|
||||
encoders.add(Bug58624Encoder.class);
|
||||
return ServerEndpointConfig.Builder.create(
|
||||
Bug58624Endpoint.class, PATH).encoders(encoders).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Bug58624Endpoint {
|
||||
|
||||
private static final ExecutorService ex = Executors.newFixedThreadPool(1);
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session) {
|
||||
// Disabling blocking timeouts for this test
|
||||
session.getUserProperties().put(
|
||||
org.apache.tomcat.websocket.Constants.BLOCKING_SEND_TIMEOUT_PROPERTY,
|
||||
Long.valueOf(-1));
|
||||
ex.submit(new Bug58624SendMessage(session));
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(String message) {
|
||||
System.out.println("OnMessage: " + message);
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Throwable t) {
|
||||
System.err.println("OnError:");
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(@SuppressWarnings("unused") Session session, CloseReason cr) {
|
||||
System.out.println("Closed " + cr);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Bug58624SendMessage implements Runnable {
|
||||
private Session session;
|
||||
|
||||
public Bug58624SendMessage(Session session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Breakpoint B required on following line
|
||||
session.getBasicRemote().sendObject("test");
|
||||
} catch (IOException | EncodeException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Bug58624Encoder implements Encoder.Text<Object> {
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(EndpointConfig endpointConfig) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(Object object) throws EncodeException {
|
||||
return (String) object;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.apache.tomcat.websocket.server;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.LifecycleState;
|
||||
import org.apache.catalina.servlets.DefaultServlet;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.tomcat.unittest.TesterServletContext;
|
||||
import org.apache.tomcat.websocket.TesterEchoServer;
|
||||
import org.apache.tomcat.websocket.TesterMessageCountClient.BasicText;
|
||||
import org.apache.tomcat.websocket.WebSocketBaseTest;
|
||||
import org.apache.tomcat.websocket.pojo.TesterUtil.SimpleClient;
|
||||
|
||||
|
||||
public class TestWsServerContainer extends WebSocketBaseTest {
|
||||
|
||||
@Test
|
||||
public void testBug54807() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
ctx.addApplicationListener(Bug54807Config.class.getName());
|
||||
Tomcat.addServlet(ctx, "default", new DefaultServlet());
|
||||
ctx.addServletMappingDecoded("/", "default");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
Assert.assertEquals(LifecycleState.STARTED, ctx.getState());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug58232() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
ctx.addApplicationListener(Bug54807Config.class.getName());
|
||||
Tomcat.addServlet(ctx, "default", new DefaultServlet());
|
||||
ctx.addServletMappingDecoded("/", "default");
|
||||
|
||||
WebSocketContainer wsContainer =
|
||||
ContainerProvider.getWebSocketContainer();
|
||||
|
||||
tomcat.start();
|
||||
|
||||
Assert.assertEquals(LifecycleState.STARTED, ctx.getState());
|
||||
|
||||
SimpleClient client = new SimpleClient();
|
||||
URI uri = new URI("ws://localhost:" + getPort() + "/echoBasic");
|
||||
|
||||
try (Session session = wsContainer.connectToServer(client, uri);) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
BasicText handler = new BasicText(latch);
|
||||
session.addMessageHandler(handler);
|
||||
session.getBasicRemote().sendText("echoBasic");
|
||||
|
||||
boolean latchResult = handler.getLatch().await(10, TimeUnit.SECONDS);
|
||||
Assert.assertTrue(latchResult);
|
||||
|
||||
Queue<String> messages = handler.getMessages();
|
||||
Assert.assertEquals(1, messages.size());
|
||||
for (String message : messages) {
|
||||
Assert.assertEquals("echoBasic", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Bug54807Config extends TesterEndpointConfig {
|
||||
|
||||
@Override
|
||||
protected ServerEndpointConfig getServerEndpointConfig() {
|
||||
return ServerEndpointConfig.Builder.create(
|
||||
TesterEchoServer.Basic.class, "/{param}").build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSpecExample3() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/a/{var}/c").build();
|
||||
ServerEndpointConfig configB = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/a/b/c").build();
|
||||
ServerEndpointConfig configC = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/a/{var1}/{var2}").build();
|
||||
|
||||
sc.addEndpoint(configA);
|
||||
sc.addEndpoint(configB);
|
||||
sc.addEndpoint(configC);
|
||||
|
||||
Assert.assertEquals(configB, sc.findMapping("/a/b/c").getConfig());
|
||||
Assert.assertEquals(configA, sc.findMapping("/a/d/c").getConfig());
|
||||
Assert.assertEquals(configC, sc.findMapping("/a/x/y").getConfig());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSpecExample4() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/{var1}/d").build();
|
||||
ServerEndpointConfig configB = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/b/{var2}").build();
|
||||
|
||||
sc.addEndpoint(configA);
|
||||
sc.addEndpoint(configB);
|
||||
|
||||
Assert.assertEquals(configB, sc.findMapping("/b/d").getConfig());
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = DeploymentException.class)
|
||||
public void testDuplicatePaths01() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/a/b/c").build();
|
||||
ServerEndpointConfig configB = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/a/b/c").build();
|
||||
|
||||
sc.addEndpoint(configA);
|
||||
sc.addEndpoint(configB);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = DeploymentException.class)
|
||||
public void testDuplicatePaths02() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/a/b/{var}").build();
|
||||
ServerEndpointConfig configB = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/a/b/{var}").build();
|
||||
|
||||
sc.addEndpoint(configA);
|
||||
sc.addEndpoint(configB);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = DeploymentException.class)
|
||||
public void testDuplicatePaths03() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/a/b/{var1}").build();
|
||||
ServerEndpointConfig configB = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/a/b/{var2}").build();
|
||||
|
||||
sc.addEndpoint(configA);
|
||||
sc.addEndpoint(configB);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDuplicatePaths04() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/a/{var1}/{var2}").build();
|
||||
ServerEndpointConfig configB = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/a/b/{var2}").build();
|
||||
|
||||
sc.addEndpoint(configA);
|
||||
sc.addEndpoint(configB);
|
||||
|
||||
Assert.assertEquals(configA, sc.findMapping("/a/x/y").getConfig());
|
||||
Assert.assertEquals(configB, sc.findMapping("/a/b/y").getConfig());
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Simulates a class that gets picked up for extending Endpoint and for
|
||||
* being annotated.
|
||||
*/
|
||||
@Test(expected = DeploymentException.class)
|
||||
public void testDuplicatePaths11() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
Pojo.class, "/foo").build();
|
||||
|
||||
sc.addEndpoint(configA, false);
|
||||
sc.addEndpoint(Pojo.class, true);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* POJO auto deployment followed by programmatic duplicate. Keep POJO.
|
||||
*/
|
||||
@Test
|
||||
public void testDuplicatePaths12() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
Pojo.class, "/foo").build();
|
||||
|
||||
sc.addEndpoint(Pojo.class, true);
|
||||
sc.addEndpoint(configA);
|
||||
|
||||
Assert.assertNotEquals(configA, sc.findMapping("/foo").getConfig());
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* POJO programmatic followed by programmatic duplicate.
|
||||
*/
|
||||
@Test(expected = DeploymentException.class)
|
||||
public void testDuplicatePaths13() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
Pojo.class, "/foo").build();
|
||||
|
||||
sc.addEndpoint(Pojo.class);
|
||||
sc.addEndpoint(configA);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* POJO auto deployment followed by programmatic on same path.
|
||||
*/
|
||||
@Test(expected = DeploymentException.class)
|
||||
public void testDuplicatePaths14() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/foo").build();
|
||||
|
||||
sc.addEndpoint(Pojo.class, true);
|
||||
sc.addEndpoint(configA);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Simulates a class that gets picked up for extending Endpoint and for
|
||||
* being annotated.
|
||||
*/
|
||||
@Test(expected = DeploymentException.class)
|
||||
public void testDuplicatePaths21() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
PojoTemplate.class, "/foo/{a}").build();
|
||||
|
||||
sc.addEndpoint(configA, false);
|
||||
sc.addEndpoint(PojoTemplate.class, true);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* POJO auto deployment followed by programmatic duplicate. Keep POJO.
|
||||
*/
|
||||
@Test
|
||||
public void testDuplicatePaths22() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
PojoTemplate.class, "/foo/{a}").build();
|
||||
|
||||
sc.addEndpoint(PojoTemplate.class, true);
|
||||
sc.addEndpoint(configA);
|
||||
|
||||
Assert.assertNotEquals(configA, sc.findMapping("/foo/{a}").getConfig());
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* POJO programmatic followed by programmatic duplicate.
|
||||
*/
|
||||
@Test(expected = DeploymentException.class)
|
||||
public void testDuplicatePaths23() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
PojoTemplate.class, "/foo/{a}").build();
|
||||
|
||||
sc.addEndpoint(PojoTemplate.class);
|
||||
sc.addEndpoint(configA);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* POJO auto deployment followed by programmatic on same path.
|
||||
*/
|
||||
@Test(expected = DeploymentException.class)
|
||||
public void testDuplicatePaths24() throws Exception {
|
||||
WsServerContainer sc = new WsServerContainer(new TesterServletContext());
|
||||
|
||||
ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
|
||||
Object.class, "/foo/{a}").build();
|
||||
|
||||
sc.addEndpoint(PojoTemplate.class, true);
|
||||
sc.addEndpoint(configA);
|
||||
}
|
||||
|
||||
|
||||
@ServerEndpoint("/foo")
|
||||
public static class Pojo {
|
||||
}
|
||||
|
||||
|
||||
@ServerEndpoint("/foo/{a}")
|
||||
public static class PojoTemplate {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.apache.tomcat.websocket.server;
|
||||
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.server.ServerContainer;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
public abstract class TesterEndpointConfig extends WsContextListener {
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
super.contextInitialized(sce);
|
||||
|
||||
ServerContainer sc = (ServerContainer) sce.getServletContext().getAttribute(
|
||||
Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
|
||||
|
||||
try {
|
||||
ServerEndpointConfig sec = getServerEndpointConfig();
|
||||
if (sec == null) {
|
||||
sc.addEndpoint(getEndpointClass());
|
||||
} else {
|
||||
sc.addEndpoint(sec);
|
||||
}
|
||||
} catch (DeploymentException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected Class<?> getEndpointClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected ServerEndpointConfig getServerEndpointConfig() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
131
test/org/apache/tomcat/websocket/server/TesterWsClient.java
Normal file
131
test/org/apache/tomcat/websocket/server/TesterWsClient.java
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.websocket.CloseReason.CloseCode;
|
||||
|
||||
/**
|
||||
* A client for testing Websocket behavior that differs from standard client
|
||||
* behavior.
|
||||
*/
|
||||
public class TesterWsClient {
|
||||
|
||||
private static final byte[] maskingKey = new byte[] { 0x12, 0x34, 0x56,
|
||||
0x78 };
|
||||
|
||||
private final Socket socket;
|
||||
|
||||
public TesterWsClient(String host, int port) throws Exception {
|
||||
this.socket = new Socket(host, port);
|
||||
// Set read timeout in case of failure so test doesn't hang
|
||||
socket.setSoTimeout(2000);
|
||||
// Disable Nagle's algorithm to ensure packets sent immediately
|
||||
// TODO: Hoping this causes writes to wait for a TCP ACK for TCP RST
|
||||
// test cases but I'm not sure?
|
||||
socket.setTcpNoDelay(true);
|
||||
}
|
||||
|
||||
public void httpUpgrade(String path) throws IOException {
|
||||
String req = createUpgradeRequest(path);
|
||||
write(req.getBytes(StandardCharsets.UTF_8));
|
||||
readUpgradeResponse();
|
||||
}
|
||||
|
||||
public void sendTextMessage(String text) throws IOException {
|
||||
sendTextMessage(text.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public void sendTextMessage(byte[] utf8Bytes) throws IOException {
|
||||
write(createFrame(true, 1, utf8Bytes));
|
||||
}
|
||||
|
||||
public void sendCloseFrame(CloseCode closeCode) throws IOException {
|
||||
int code = closeCode.getCode();
|
||||
byte[] codeBytes = new byte[2];
|
||||
codeBytes[0] = (byte) (code >> 8);
|
||||
codeBytes[1] = (byte) code;
|
||||
write(createFrame(true, 8, codeBytes));
|
||||
}
|
||||
|
||||
private void readUpgradeResponse() throws IOException {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||
socket.getInputStream()));
|
||||
String line = in.readLine();
|
||||
while (line != null && !line.isEmpty()) {
|
||||
line = in.readLine();
|
||||
}
|
||||
}
|
||||
|
||||
public void closeSocket() throws IOException {
|
||||
// Enable SO_LINGER to ensure close() only returns when TCP closing
|
||||
// handshake completes
|
||||
socket.setSoLinger(true, 65535);
|
||||
socket.close();
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a TCP RST instead of a TCP closing handshake
|
||||
*/
|
||||
public void forceCloseSocket() throws IOException {
|
||||
// SO_LINGER sends a TCP RST when timeout expires
|
||||
socket.setSoLinger(true, 0);
|
||||
socket.close();
|
||||
}
|
||||
|
||||
private void write(byte[] bytes) throws IOException {
|
||||
socket.getOutputStream().write(bytes);
|
||||
socket.getOutputStream().flush();
|
||||
}
|
||||
|
||||
private static String createUpgradeRequest(String path) {
|
||||
String[] upgradeRequestLines = { "GET " + path + " HTTP/1.1",
|
||||
"Connection: Upgrade", "Host: localhost:8080",
|
||||
"Origin: localhost:8080",
|
||||
"Sec-WebSocket-Key: OEvAoAKn5jsuqv2/YJ1Wfg==",
|
||||
"Sec-WebSocket-Version: 13", "Upgrade: websocket" };
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (String line : upgradeRequestLines) {
|
||||
sb.append(line);
|
||||
sb.append("\r\n");
|
||||
}
|
||||
sb.append("\r\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static byte[] createFrame(boolean fin, int opCode, byte[] payload) {
|
||||
byte[] frame = new byte[6 + payload.length];
|
||||
frame[0] = (byte) (opCode | (fin ? 1 << 7 : 0));
|
||||
frame[1] = (byte) (0x80 | payload.length);
|
||||
|
||||
frame[2] = maskingKey[0];
|
||||
frame[3] = maskingKey[1];
|
||||
frame[4] = maskingKey[2];
|
||||
frame[5] = maskingKey[3];
|
||||
|
||||
for (int i = 0; i < payload.length; i++) {
|
||||
frame[i + 6] = (byte) (payload[i] ^ maskingKey[i % 4]);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user