/* * Copyright (c) 2008-2017 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.cometd.tests; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import javax.websocket.ContainerProvider; import javax.websocket.WebSocketContainer; import org.cometd.bayeux.Channel; import org.cometd.bayeux.Message; import org.cometd.bayeux.client.ClientSessionChannel; import org.cometd.bayeux.server.BayeuxServer; import org.cometd.bayeux.server.ServerMessage; import org.cometd.bayeux.server.ServerSession; import org.cometd.client.BayeuxClient; import org.cometd.client.transport.ClientTransport; import org.cometd.client.transport.LongPollingTransport; import org.cometd.server.BayeuxServerImpl; import org.cometd.server.CometDServlet; import org.cometd.websocket.client.WebSocketTransport; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; import org.junit.After; import org.junit.Assert; import org.junit.Test; public class TransportFailureTest { private Server server; private ServerConnector connector; private String cometdURL; private BayeuxServerImpl bayeux; private HttpClient httpClient; private WebSocketContainer wsClient; private String cometdServletPath; private void startServer(Map<String, String> initParams) throws Exception { server = new Server(); connector = new ServerConnector(server); server.addConnector(connector); ServletContextHandler context = new ServletContextHandler(server, "/"); WebSocketServerContainerInitializer.configureContext(context); cometdServletPath = "/cometd"; // CometD servlet ServletHolder cometdServletHolder = new ServletHolder(CometDServlet.class); cometdServletHolder.setInitParameter("timeout", "10000"); cometdServletHolder.setInitParameter("ws.cometdURLMapping", cometdServletPath); cometdServletHolder.setInitOrder(1); if (initParams != null) { for (Map.Entry<String, String> entry : initParams.entrySet()) { cometdServletHolder.setInitParameter(entry.getKey(), entry.getValue()); } } context.addServlet(cometdServletHolder, cometdServletPath + "/*"); httpClient = new HttpClient(); server.addBean(httpClient); wsClient = ContainerProvider.getWebSocketContainer(); server.addBean(wsClient); server.start(); cometdURL = "http://localhost:" + connector.getLocalPort() + cometdServletPath; bayeux = (BayeuxServerImpl)context.getServletContext().getAttribute(BayeuxServer.ATTRIBUTE); } @After public void dispose() throws Exception { if (server != null) { server.stop(); } } @Test public void testTransportNegotiationClientWebSocketAndLongPollingServerLongPolling() throws Exception { startServer(null); bayeux.setAllowedTransports("long-polling"); final ClientTransport webSocketTransport = new WebSocketTransport(null, null, wsClient); final ClientTransport longPollingTransport = new LongPollingTransport(null, httpClient); final CountDownLatch failureLatch = new CountDownLatch(1); final BayeuxClient client = new BayeuxClient(cometdURL, webSocketTransport, longPollingTransport) { @Override protected void onTransportFailure(String oldTransportName, String newTransportName, Throwable failure) { Assert.assertEquals(webSocketTransport.getName(), oldTransportName); Assert.assertEquals(longPollingTransport.getName(), newTransportName); failureLatch.countDown(); } }; final CountDownLatch successLatch = new CountDownLatch(1); final CountDownLatch failedLatch = new CountDownLatch(1); client.handshake(new ClientSessionChannel.MessageListener() { @Override public void onMessage(ClientSessionChannel channel, Message message) { if (message.isSuccessful()) { successLatch.countDown(); } else { failedLatch.countDown(); } } }); Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(failedLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS)); client.disconnect(1000); } @Test public void testTransportNegotiationFailureForClientWebSocketServerLongPolling() throws Exception { startServer(null); bayeux.setAllowedTransports("long-polling"); final ClientTransport webSocketTransport = new WebSocketTransport(null, null, wsClient); final CountDownLatch failureLatch = new CountDownLatch(1); final BayeuxClient client = new BayeuxClient(cometdURL, webSocketTransport) { @Override protected void onTransportFailure(String oldTransportName, String newTransportName, Throwable failure) { Assert.assertEquals(webSocketTransport.getName(), oldTransportName); Assert.assertNull(newTransportName); failureLatch.countDown(); } }; final CountDownLatch failedLatch = new CountDownLatch(1); client.handshake(new ClientSessionChannel.MessageListener() { @Override public void onMessage(ClientSessionChannel channel, Message message) { if (!message.isSuccessful()) { failedLatch.countDown(); } } }); Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(failedLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(client.waitFor(5000, BayeuxClient.State.DISCONNECTED)); } @Test public void testTransportNegotiationFailureForClientLongPollingServerCallbackPolling() throws Exception { startServer(null); // Only callback-polling on server (via extension), only long-polling on client. bayeux.setAllowedTransports("long-polling", "callback-polling"); bayeux.addExtension(new BayeuxServer.Extension.Adapter() { @Override public boolean sendMeta(ServerSession to, ServerMessage.Mutable message) { if (Channel.META_HANDSHAKE.equals(message.getChannel())) { message.put(Message.SUPPORTED_CONNECTION_TYPES_FIELD, new String[]{"callback-polling"}); } return true; } }); final CountDownLatch failureLatch = new CountDownLatch(1); BayeuxClient client = new BayeuxClient(cometdURL, new LongPollingTransport(null, httpClient)) { @Override protected void onTransportFailure(String oldTransportName, String newTransportName, Throwable failure) { failureLatch.countDown(); } }; final CountDownLatch latch = new CountDownLatch(1); client.handshake(new ClientSessionChannel.MessageListener() { @Override public void onMessage(ClientSessionChannel channel, Message message) { if (!message.isSuccessful()) { latch.countDown(); } } }); Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(client.waitFor(5000, BayeuxClient.State.DISCONNECTED)); } @Test public void testTransportNegotiationFailureForClientLongPollingServerWebSocket() throws Exception { startServer(null); bayeux.setAllowedTransports("websocket"); final ClientTransport longPollingTransport = new LongPollingTransport(null, httpClient); final CountDownLatch failureLatch = new CountDownLatch(1); final BayeuxClient client = new BayeuxClient(cometdURL, longPollingTransport) { @Override protected void onTransportFailure(String oldTransportName, String newTransportName, Throwable failure) { Assert.assertEquals(longPollingTransport.getName(), oldTransportName); Assert.assertEquals(longPollingTransport.getName(), newTransportName); failureLatch.countDown(); } }; final CountDownLatch failedLatch = new CountDownLatch(1); client.handshake(new ClientSessionChannel.MessageListener() { @Override public void onMessage(ClientSessionChannel channel, Message message) { if (!message.isSuccessful()) { failedLatch.countDown(); } } }); Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(failedLatch.await(5, TimeUnit.SECONDS)); client.disconnect(1000); } @Test public void testChangeTransportURLOnMetaConnectFailure() throws Exception { startServer(null); ServerConnector connector2 = new ServerConnector(server); server.addConnector(connector2); connector2.start(); bayeux.addExtension(new MetaConnectFailureExtension() { @Override protected boolean onMetaConnect(int count) throws Exception { if (count != 2) { return true; } connector.stop(); return false; } }); final String newURL = "http://localhost:" + connector2.getLocalPort() + cometdServletPath; final BayeuxClient client = new BayeuxClient(cometdURL, new LongPollingTransport(null, httpClient)) { private int metaConnects; @Override protected void onTransportFailure(Message message, ClientTransport.FailureInfo failureInfo, ClientTransport.FailureHandler handler) { ++metaConnects; if (metaConnects == 1 && Channel.META_CONNECT.equals(message.getChannel())) { ClientTransport transport = getTransport(); transport.setURL(newURL); failureInfo.transport = transport; handler.handle(failureInfo); } else { super.onTransportFailure(message, failureInfo, handler); } } }; // The second connect fails, the third connect should succeed on the new URL. final CountDownLatch latch = new CountDownLatch(1); client.getChannel(Channel.META_CONNECT).addListener(new ClientSessionChannel.MessageListener() { private int metaConnects; @Override public void onMessage(ClientSessionChannel channel, Message message) { ++metaConnects; if (metaConnects == 3 && message.isSuccessful()) { if (client.getTransport().getURL().equals(newURL)) { latch.countDown(); } } } }); client.handshake(); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); client.disconnect(1000); connector2.stop(); } @Test public void testChangeTransportOnMetaConnectFailure() throws Exception { startServer(null); bayeux.addExtension(new MetaConnectFailureExtension() { @Override protected boolean onMetaConnect(int count) throws Exception { return count != 2; } }); final ClientTransport webSocketTransport = new WebSocketTransport(null, null, wsClient); LongPollingTransport longPollingTransport = new LongPollingTransport(null, httpClient); final BayeuxClient client = new BayeuxClient(cometdURL, webSocketTransport, longPollingTransport) { private int metaConnects; @Override protected void onTransportFailure(Message message, ClientTransport.FailureInfo failureInfo, ClientTransport.FailureHandler handler) { ++metaConnects; if (metaConnects == 1 && Channel.META_CONNECT.equals(message.getChannel())) { failureInfo.transport = webSocketTransport; handler.handle(failureInfo); } else { super.onTransportFailure(message, failureInfo, handler); } } }; // The second connect fails, the third connect should succeed on the new transport. final CountDownLatch latch = new CountDownLatch(1); client.getChannel(Channel.META_CONNECT).addListener(new ClientSessionChannel.MessageListener() { private int metaConnects; @Override public void onMessage(ClientSessionChannel channel, Message message) { ++metaConnects; if (metaConnects == 3 && message.isSuccessful()) { if (client.getTransport().getName().equals(webSocketTransport.getName())) { latch.countDown(); } } } }); client.handshake(); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); client.disconnect(1000); } private abstract class MetaConnectFailureExtension extends BayeuxServer.Extension.Adapter { private final AtomicInteger metaConnects = new AtomicInteger(); @Override public boolean rcvMeta(ServerSession from, ServerMessage.Mutable message) { if (Channel.META_CONNECT.equals(message.getChannel())) { try { return onMetaConnect(metaConnects.incrementAndGet()); } catch (Exception x) { throw new RuntimeException(x); } } return true; } protected abstract boolean onMetaConnect(int count) throws Exception; } }