/* * Copyright 2017 Async-IO.org * * 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.atmosphere.tests; import com.ning.http.client.AsyncHttpClient; import com.ning.http.client.AsyncHttpClientConfig; import org.atmosphere.cpr.ApplicationConfig; import org.atmosphere.cpr.AtmosphereHandler; import org.atmosphere.cpr.AtmosphereResource; import org.atmosphere.cpr.AtmosphereResourceEvent; import org.atmosphere.cpr.AtmosphereResourceEventListenerAdapter; import org.atmosphere.nettosphere.Config; import org.atmosphere.nettosphere.Nettosphere; import org.atmosphere.wasync.ClientFactory; import org.atmosphere.wasync.Event; import org.atmosphere.wasync.Function; import org.atmosphere.wasync.Request; import org.atmosphere.wasync.RequestBuilder; import org.atmosphere.wasync.Socket; import org.atmosphere.wasync.impl.AtmosphereClient; import org.atmosphere.wasync.serial.DefaultSerializedFireStage; import org.atmosphere.wasync.serial.SerializedClient; import org.atmosphere.wasync.serial.SerializedOptionsBuilder; import org.testng.annotations.Test; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; public class LongPollingTest extends StreamingTest { @Override Request.TRANSPORT transport() { return Request.TRANSPORT.LONG_POLLING; } @Override int statusCode() { return 200; } @Override int notFoundCode() { return 404; } @Test public void BinaryEchoTest() throws Exception { logger.info("\n\nBinaryEchoTest\n\n"); final CountDownLatch suspendedLatch = new CountDownLatch(2); Config config = new Config.Builder() .port(port) .host("127.0.0.1") .resource("/suspend", new AtmosphereHandler() { @Override public void onRequest(AtmosphereResource resource) throws IOException { if (resource.getRequest().getMethod().equals("GET")) { resource.addEventListener(new AtmosphereResourceEventListenerAdapter.OnSuspend() { @Override public void onSuspend(AtmosphereResourceEvent event) { suspendedLatch.countDown(); } }).suspend(-1); } else { int payloadSize = resource.getRequest().getContentLength(); byte[] payload = new byte[payloadSize]; try { resource.getRequest().getInputStream().read(payload); } catch (Exception e) { e.printStackTrace(); } logger.info("echoing : {}", payload); resource.getBroadcaster().broadcast(payload); } } @Override public void onStateChange(AtmosphereResourceEvent event) throws IOException { if (!(event.isResuming()) || event.isResumedOnTimeout() || event.isSuspended()) { // make the GET reply have binary content type event.getResource().getResponse().setContentType("application/octet-stream"); // make it use the OutputStream directly in writing, prevent any String conversions. event.getResource().getRequest().setAttribute(ApplicationConfig.PROPERTY_USE_STREAM, true); // do the actual write event.getResource().getResponse().write((byte[]) event.getMessage()); event.getResource().resume(); } } @Override public void destroy() { } }).build(); Nettosphere server = new Nettosphere.Builder().config(config).build(); assertNotNull(server); server.start(); final CountDownLatch latch = new CountDownLatch(1); final AtomicBoolean hasEchoReplied = new AtomicBoolean(false); AtmosphereClient client = ClientFactory.getDefault().newClient(AtmosphereClient.class); final byte[] binaryEcho = new byte[]{1, 2, 3, 4}; RequestBuilder request = client.newRequestBuilder() .method(Request.METHOD.GET) .uri(targetUrl + "/suspend") .header("Content-Type", "application/octet-stream") .transport(Request.TRANSPORT.LONG_POLLING); final Socket socket = client.create(client.newOptionsBuilder().runtime(ahc, false).build()); socket.on(new Function<Integer>() { @Override public void on(Integer statusCode) { suspendedLatch.countDown(); } }).on("message", new Function<byte[]>() { @Override public void on(byte[] message) { logger.info("===Received : {}", message); if (Arrays.equals(message, binaryEcho) && !hasEchoReplied.get()) { hasEchoReplied.getAndSet(true); socket.close(); latch.countDown(); } } }).on(new Function<Throwable>() { @Override public void on(Throwable t) { t.printStackTrace(); } }).open(request.build()); suspendedLatch.await(5, TimeUnit.SECONDS); socket.fire(binaryEcho).get(); latch.await(10, TimeUnit.SECONDS); assertEquals(hasEchoReplied.get(), true); } @Test(enabled = true) public void serializeTest() throws Exception { System.out.println("=============== STARTING SerializedTest"); if (server != null) { server.stop(); } Config config = new Config.Builder() .port(port) .host("127.0.0.1") .resource("/suspend", new AtmosphereHandler() { private final AtomicInteger count = new AtomicInteger(2); private final AtomicReference<StringBuffer> response = new AtomicReference<StringBuffer>(new StringBuffer()); @Override public void onRequest(AtmosphereResource r) throws IOException { if (r.getRequest().getMethod().equalsIgnoreCase("GET")) { r.suspend(-1); } else { try { r.getBroadcaster().broadcast(r.getRequest().getReader().readLine()).get(); } catch (InterruptedException e) { logger.error("", e); } catch (ExecutionException e) { logger.error("", e); } } } @Override public void onStateChange(AtmosphereResourceEvent r) throws IOException { if (r.getMessage() != null) { response.get().append(r.getMessage()); if (count.decrementAndGet() == 0) { r.getResource().getResponse().write(response.toString()); r.getResource().resume(); } } } @Override public void destroy() { } }).build(); server = new Nettosphere.Builder().config(config).build(); assertNotNull(server); server.start(); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<StringBuffer> response = new AtomicReference<StringBuffer>(new StringBuffer()); SerializedClient client = ClientFactory.getDefault().newClient(SerializedClient.class); SerializedOptionsBuilder b = client.newOptionsBuilder(); b.serializedFireStage(new DefaultSerializedFireStage()); RequestBuilder request = client.newRequestBuilder() .method(Request.METHOD.GET) .uri(targetUrl + "/suspend") .transport(transport()); Socket socket = client.create(b.build()); socket.on("message", new Function<String>() { @Override public void on(String t) { logger.info("Serialized Function invoked {}", t); response.get().append(t); latch.countDown(); } }).on(new Function<Throwable>() { @Override public void on(Throwable t) { System.out.println("=============== ERROR"); logger.error("", t); } }).open(request.build()) .fire("PING") .fire("PONG").get(5, TimeUnit.SECONDS); latch.await(5, TimeUnit.SECONDS); socket.close(); assertEquals(response.get().toString(), "PINGPONG"); } /** * Due to the reconnection cycle, this test may or may not work on Jenkins due ti the connection latency. Better to disable it. * @throws Exception */ @Test(enabled = false) public void noMessageLostTest() throws Exception { logger.info("\n\nnoMessageLostTest\n\n"); final CountDownLatch suspendedLatch = new CountDownLatch(2); Config config = new Config.Builder() .port(port) .host("127.0.0.1") .broadcasterCache(org.atmosphere.cache.UUIDBroadcasterCache.class) .resource("/suspend", new AtmosphereHandler() { @Override public void onRequest(AtmosphereResource resource) throws IOException { if (resource.getRequest().getMethod().equals("GET")) { logger.info("Suspending : {}", resource.uuid()); resource.addEventListener(new AtmosphereResourceEventListenerAdapter.OnSuspend() { @Override public void onSuspend(AtmosphereResourceEvent event) { suspendedLatch.countDown(); } }).suspend(-1); } else { String echo = resource.getRequest().getReader().readLine(); logger.info("echoing : {}", echo); try { resource.getBroadcaster().broadcast(echo).get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } @Override public void onStateChange(AtmosphereResourceEvent event) throws IOException { logger.info("cached : {}", event.getMessage()); if (event.getMessage() != null && List.class.isAssignableFrom(event.getMessage().getClass())) { List<String> cached = (List<String>) List.class.cast(event.getMessage()); StringBuilder b = new StringBuilder(); for (String m : cached) { b.append(m).append("-"); } // Write message in a single IO operation to prevent the connection from resuming. event.getResource().getResponse().write(b.toString()); } else { event.getResource().getResponse().write((String) event.getMessage() + "-"); } event.getResource().resume(); } @Override public void destroy() { } }).build(); Nettosphere server = new Nettosphere.Builder().config(config).build(); assertNotNull(server); server.start(); final CountDownLatch latch = new CountDownLatch(5); final AtomicReference<Set> response = new AtomicReference<Set>(new HashSet()); AtmosphereClient client = ClientFactory.getDefault().newClient(AtmosphereClient.class); RequestBuilder request = client.newRequestBuilder() .method(Request.METHOD.GET) .uri(targetUrl + "/suspend") .transport(Request.TRANSPORT.LONG_POLLING); final Socket socket = client.create(client.newOptionsBuilder().runtime(ahc, false).build()); socket.on(new Function<Integer>() { @Override public void on(Integer statusCode) { suspendedLatch.countDown(); } }).on("message", new Function<String>() { @Override public void on(String message) { logger.info("received : {}", message); for (String m : message.split("-")) { response.get().add(m); latch.countDown(); } } }).on(new Function<Throwable>() { @Override public void on(Throwable t) { t.printStackTrace(); } }).open(request.build()); suspendedLatch.await(5, TimeUnit.SECONDS); socket.fire("ECHO1"); socket.fire("ECHO2"); socket.fire("ECHO3"); socket.fire("ECHO4"); socket.fire("ECHO5"); latch.await(10, TimeUnit.SECONDS); logger.info("RESPONSE {}", response.get()); assertEquals(response.get().size(), 5); socket.close(); server.stop(); } @Test(enabled = true) public void serializeFutureGetTest() throws Exception { logger.info("\n\nserializeFutureGetTest\n\n"); final CountDownLatch latch = new CountDownLatch(4); final AtomicInteger allMessagesReceived = new AtomicInteger(); Config config = new Config.Builder() .port(port) .host("127.0.0.1") .resource("/suspend", new AtmosphereHandler() { @Override public void onRequest(final AtmosphereResource r) throws IOException { if (r.getRequest().getMethod().equalsIgnoreCase("GET")) { r.addEventListener(new AtmosphereResourceEventListenerAdapter() { @Override public void onSuspend(AtmosphereResourceEvent event) { latch.countDown(); } }).suspend(); } else { try { String msg = r.getRequest().getReader().readLine(); // In case the message arrive in a single chunk. if (msg.equalsIgnoreCase("PINGPONG")) { r.getBroadcaster().broadcast("PING").get(); r.getBroadcaster().broadcast("PONG").get(); } else { r.getBroadcaster().broadcast(msg).get(); } } catch (InterruptedException e) { logger.error("", e); ; } catch (ExecutionException e) { logger.error("", e); } } } @Override public void onStateChange(AtmosphereResourceEvent r) throws IOException { if (r.getMessage() != null) { r.getResource().getResponse().write(r.getMessage().toString()); // If the connection is about to resume we will loose the message so we must make sure we got 2 messages // before resuming. if (allMessagesReceived.incrementAndGet() == 2 && r.getResource().transport().equals(AtmosphereResource.TRANSPORT.LONG_POLLING)) { r.getResource().getResponse().flushBuffer(); r.getResource().resume(); } } } @Override public void destroy() { } }).build(); server = new Nettosphere.Builder().config(config).build(); assertNotNull(server); server.start(); final AtomicReference<StringBuffer> response = new AtomicReference<StringBuffer>(new StringBuffer()); SerializedClient client = ClientFactory.getDefault().newClient(SerializedClient.class); SerializedOptionsBuilder b = client.newOptionsBuilder().runtime(ahc, false); b.serializedFireStage(new DefaultSerializedFireStage()); RequestBuilder request = client.newRequestBuilder() .method(Request.METHOD.GET) .uri(targetUrl + "/suspend") .transport(transport()); Socket socket = client.create(b.build()); socket.on("message", new Function<String>() { @Override public void on(String t) { logger.info("Serialized Function get invoked {}", t); response.get().append(t); latch.countDown(); // If we received the message in a single packet if (t.equals("PINGPONG")) latch.countDown(); } }).on(Event.OPEN.name(), new Function<Object>() { @Override public void on(Object o) { latch.countDown(); } }).open(request.build()) .fire("PING") .fire("PONG").get(5, TimeUnit.SECONDS); latch.await(10, TimeUnit.SECONDS); socket.close(); assertEquals(response.get().toString(), "PINGPONG"); } @Test public void ahcCloseTest2() throws IOException, InterruptedException { Config config = new Config.Builder() .port(port) .host("127.0.0.1") .resource("/suspend", new AtmosphereHandler() { private final AtomicBoolean b = new AtomicBoolean(false); @Override public void onRequest(AtmosphereResource r) throws IOException { r.suspend(); } @Override public void onStateChange(AtmosphereResourceEvent r) throws IOException { } @Override public void destroy() { } }).build(); server = new Nettosphere.Builder().config(config).build(); assertNotNull(server); server.start(); final AsyncHttpClient ahc = new AsyncHttpClient(new AsyncHttpClientConfig.Builder().setMaxRequestRetry(0).build()); SerializedClient client = ClientFactory.getDefault().newClient(SerializedClient.class); RequestBuilder request = client.newRequestBuilder() .method(Request.METHOD.GET) .uri(targetUrl + "/suspend") .transport(Request.TRANSPORT.WEBSOCKET); Socket socket = client.create(client.newOptionsBuilder().runtime(ahc, false).runtimeShared(false).serializedFireStage(new DefaultSerializedFireStage()).build()); socket.open(request.build()); socket.close(); // AHC is async closed Thread.sleep(1000); assertTrue(ahc.isClosed()); } }