/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* 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.
* #L%
*/
package org.wisdom.framework.vertx;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientOptions;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.junit.After;
import org.junit.Test;
import org.wisdom.api.Controller;
import org.wisdom.api.DefaultController;
import org.wisdom.api.configuration.ApplicationConfiguration;
import org.wisdom.api.content.ContentEngine;
import org.wisdom.api.exceptions.ExceptionMapper;
import org.wisdom.api.http.HttpMethod;
import org.wisdom.api.http.Request;
import org.wisdom.api.http.Result;
import org.wisdom.api.router.Route;
import org.wisdom.api.router.RouteBuilder;
import org.wisdom.api.router.Router;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
/**
* Check that we generate correct chunked responses.
*/
public class ChunkedResponseTest extends VertxBaseTest {
private WisdomVertxServer server;
@After
public void tearDown() {
if (server != null) {
server.stop();
server = null;
}
}
@Test
public void testChunkedResponses() throws InterruptedException, IOException {
// Prepare the configuration
ApplicationConfiguration configuration = mock(ApplicationConfiguration.class);
when(configuration.getIntegerWithDefault(eq("vertx.http.port"), anyInt())).thenReturn(0);
when(configuration.getIntegerWithDefault(eq("vertx.https.port"), anyInt())).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.acceptBacklog", -1)).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.receiveBufferSize", -1)).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.sendBufferSize", -1)).thenReturn(-1);
when(configuration.getStringArray("wisdom.websocket.subprotocols")).thenReturn(new String[0]);
when(configuration.getStringArray("vertx.websocket-subprotocols")).thenReturn(new String[0]);
// Prepare the router with a controller
Controller controller = new DefaultController() {
@SuppressWarnings("unused")
public Result index() {
int count = context().parameterAsInteger("id") * 1000;
byte[] content = new byte[count];
RANDOM.nextBytes(content);
return ok(new ByteArrayInputStream(content));
}
};
Router router = mock(Router.class);
Route route = new RouteBuilder().route(HttpMethod.GET)
.on("/")
.to(controller, "index");
when(router.getRouteFor(anyString(), anyString(), any(Request.class))).thenReturn(route);
ContentEngine contentEngine = getMockContentEngine();
// Configure the server.
server = new WisdomVertxServer();
server.configuration = configuration;
server.accessor = new ServiceAccessor(
null,
configuration,
router,
contentEngine,
executor,
null,
Collections.<ExceptionMapper>emptyList()
);
server.vertx = vertx;
server.start();
VertxHttpServerTest.waitForStart(server);
// Now start bunch of clients
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(NUMBER_OF_CLIENTS);
int port = server.httpPort();
for (int i = 1; i < NUMBER_OF_CLIENTS + 1; ++i) // create and start threads
executor.submit(new Client(startSignal, doneSignal, port, i));
startSignal.countDown(); // let all threads proceed
doneSignal.await(60, TimeUnit.SECONDS); // wait for all to finish
assertThat(failure).isEmpty();
assertThat(success).hasSize(NUMBER_OF_CLIENTS);
}
@Test
public void testAsyncChunkedResponses() throws InterruptedException, IOException {
// Prepare the configuration
ApplicationConfiguration configuration = mock(ApplicationConfiguration.class);
when(configuration.getIntegerWithDefault(eq("vertx.http.port"), anyInt())).thenReturn(0);
when(configuration.getIntegerWithDefault(eq("vertx.https.port"), anyInt())).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.acceptBacklog", -1)).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.receiveBufferSize", -1)).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.sendBufferSize", -1)).thenReturn(-1);
when(configuration.getStringArray("wisdom.websocket.subprotocols")).thenReturn(new String[0]);
when(configuration.getStringArray("vertx.websocket-subprotocols")).thenReturn(new String[0]);
// Prepare the router with a controller
Controller controller = new DefaultController() {
@SuppressWarnings("unused")
public Result index() {
return async(() -> {
int count = context().parameterAsInteger("id") * 1000;
byte[] content = new byte[count];
RANDOM.nextBytes(content);
return ok(new ByteArrayInputStream(content));
});
}
};
Router router = mock(Router.class);
Route route = new RouteBuilder().route(HttpMethod.GET)
.on("/")
.to(controller, "index");
when(router.getRouteFor(anyString(), anyString(), any(Request.class))).thenReturn(route);
// Configure the server.
server = new WisdomVertxServer();
server.configuration = configuration;
server.accessor = new ServiceAccessor(
null,
configuration,
router,
getMockContentEngine(),
executor,
null,
Collections.<ExceptionMapper>emptyList()
);
server.vertx = vertx;
server.start();
VertxHttpServerTest.waitForStart(server);
// Now start bunch of clients
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(NUMBER_OF_CLIENTS);
int port = server.httpPort();
for (int i = 1; i < NUMBER_OF_CLIENTS + 1; ++i) // create and start threads
executor.submit(new Client(startSignal, doneSignal, port, i));
startSignal.countDown(); // let all threads proceed
doneSignal.await(60, TimeUnit.SECONDS); // wait for all to finish
assertThat(failure).isEmpty();
assertThat(success).hasSize(NUMBER_OF_CLIENTS);
}
@Test
public void testZippedFileDownload() throws InterruptedException, IOException {
// Prepare the configuration
ApplicationConfiguration configuration = mock(ApplicationConfiguration.class);
when(configuration.getIntegerWithDefault(eq("vertx.http.port"), anyInt())).thenReturn(0);
when(configuration.getIntegerWithDefault(eq("vertx.https.port"), anyInt())).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.acceptBacklog", -1)).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.receiveBufferSize", -1)).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.sendBufferSize", -1)).thenReturn(-1);
when(configuration.getStringArray("wisdom.websocket.subprotocols")).thenReturn(new String[0]);
when(configuration.getStringArray("vertx.websocket-subprotocols")).thenReturn(new String[0]);
// Prepare the router with a controller
Controller controller = new DefaultController() {
@SuppressWarnings("unused")
public Result index() throws IOException {
ZipFile zip = new ZipFile("src/test/resources/owl.png.zip");
ZipEntry entry = zip.getEntry("owl.png");
InputStream stream = zip.getInputStream(entry);
return ok(stream);
}
};
Router router = mock(Router.class);
final Route route = new RouteBuilder().route(HttpMethod.GET)
.on("/")
.to(controller, "index");
doAnswer(invocationOnMock -> route).when(router).getRouteFor(anyString(), anyString(), any(Request.class));
// Configure the server.
server = new WisdomVertxServer();
server.configuration = configuration;
server.accessor = new ServiceAccessor(
null,
configuration,
router,
getMockContentEngine(),
executor,
null,
Collections.<ExceptionMapper>emptyList()
);
server.vertx = vertx;
server.start();
VertxHttpServerTest.waitForStart(server);
// Now start bunch of clients
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(NUMBER_OF_CLIENTS);
int port = server.httpPort();
for (int i = 0; i < NUMBER_OF_CLIENTS; ++i) {// create and start threads
clients.submit(new DownloadClient(startSignal, doneSignal, port, i));
}
startSignal.countDown(); // let all threads proceed
assertThat(doneSignal.await(60, TimeUnit.SECONDS)).isTrue();
assertThat(failure).isEmpty();
assertThat(success).hasSize(NUMBER_OF_CLIENTS);
}
@Test
public void testFileDownload() throws InterruptedException, IOException {
// Prepare the configuration
ApplicationConfiguration configuration = mock(ApplicationConfiguration.class);
when(configuration.getIntegerWithDefault(eq("vertx.http.port"), anyInt())).thenReturn(0);
when(configuration.getIntegerWithDefault(eq("vertx.https.port"), anyInt())).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.acceptBacklog", -1)).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.receiveBufferSize", -1)).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.sendBufferSize", -1)).thenReturn(-1);
when(configuration.getStringArray("wisdom.websocket.subprotocols")).thenReturn(new String[0]);
when(configuration.getStringArray("vertx.websocket-subprotocols")).thenReturn(new String[0]);
// Prepare the router with a controller
Controller controller = new DefaultController() {
@SuppressWarnings("unused")
public Result index() throws IOException {
File file = new File("src/test/resources/owl.png");
return ok(file);
}
};
Router router = mock(Router.class);
final Route route = new RouteBuilder().route(HttpMethod.GET)
.on("/")
.to(controller, "index");
doAnswer(invocationOnMock -> route).when(router).getRouteFor(anyString(), anyString(), any(Request.class));
// Configure the server.
server = new WisdomVertxServer();
server.configuration = configuration;
server.accessor = new ServiceAccessor(
null,
configuration,
router,
getMockContentEngine(),
executor,
null,
Collections.<ExceptionMapper>emptyList()
);
server.vertx = vertx;
server.start();
VertxHttpServerTest.waitForStart(server);
// Now start bunch of clients
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(NUMBER_OF_CLIENTS);
int port = server.httpPort();
for (int i = 0; i < NUMBER_OF_CLIENTS; ++i) // create and start threads
clients.submit(new DownloadClient(startSignal, doneSignal, port, i));
startSignal.countDown(); // let all threads proceed
doneSignal.await(60, TimeUnit.SECONDS); // wait for all to finish
assertThat(failure).isEmpty();
assertThat(success).hasSize(NUMBER_OF_CLIENTS);
}
@Test
public void testFileAsUrlDownload() throws InterruptedException, IOException {
// Prepare the configuration
ApplicationConfiguration configuration = mock(ApplicationConfiguration.class);
when(configuration.getIntegerWithDefault(eq("vertx.http.port"), anyInt())).thenReturn(0);
when(configuration.getIntegerWithDefault(eq("vertx.https.port"), anyInt())).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.acceptBacklog", -1)).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.receiveBufferSize", -1)).thenReturn(-1);
when(configuration.getIntegerWithDefault("vertx.sendBufferSize", -1)).thenReturn(-1);
when(configuration.getStringArray("wisdom.websocket.subprotocols")).thenReturn(new String[0]);
when(configuration.getStringArray("vertx.websocket-subprotocols")).thenReturn(new String[0]);
// Prepare the router with a controller
Controller controller = new DefaultController() {
@SuppressWarnings("unused")
public Result index() throws IOException {
File file = new File("src/test/resources/owl.png");
return ok(file.toURI().toURL());
}
};
Router router = mock(Router.class);
final Route route = new RouteBuilder().route(HttpMethod.GET)
.on("/")
.to(controller, "index");
doAnswer(invocationOnMock -> route).when(router).getRouteFor(anyString(), anyString(), any(Request.class));
// Configure the server.
server = new WisdomVertxServer();
server.configuration = configuration;
server.accessor = new ServiceAccessor(
null,
configuration,
router,
getMockContentEngine(),
executor,
null,
Collections.<ExceptionMapper>emptyList()
);
server.vertx = vertx;
server.start();
VertxHttpServerTest.waitForStart(server);
// Now start bunch of clients
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(NUMBER_OF_CLIENTS);
int port = server.httpPort();
for (int i = 0; i < NUMBER_OF_CLIENTS; ++i) {
// create and start threads
clients.execute(new DownloadClient(startSignal, doneSignal, port, i));
}
startSignal.countDown(); // let all threads proceed
assertThat(doneSignal.await(60, TimeUnit.SECONDS)).isTrue();
assertThat(failure).isEmpty();
assertThat(success).hasSize(NUMBER_OF_CLIENTS);
}
private class DownloadClient implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
private final int port;
private final int id;
DownloadClient(CountDownLatch startSignal, CountDownLatch doneSignal, int port, int id) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
this.port = port;
this.id = id;
}
public void run() {
try {
startSignal.await();
doWork();
} catch (Throwable ex) {
ex.printStackTrace();
fail(id);
}
doneSignal.countDown();
}
void doWork() throws IOException {
URL url;
if (id % 3 == 0) {
url = new URL("http://localhost:" + port + "/3");
} else if (id % 2 == 0) {
url = new URL("http://localhost:" + port + "/2");
} else {
url = new URL("http://localhost:" + port);
}
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (!isOk(connection.getResponseCode())) {
System.err.println("Bad error code for " + id + " got : " + connection.getResponseCode());
fail(id);
return;
}
byte[] body = IOUtils.toByteArray(connection.getInputStream());
final File img = new File("src/test/resources/owl.png");
byte[] expected = FileUtils.readFileToByteArray(img);
if (!containsExactly(body, expected)) {
System.err.println("Bad content for " + id);
fail(id);
return;
}
success(id);
}
}
private class Client implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
private final int port;
private final int id;
Client(CountDownLatch startSignal, CountDownLatch doneSignal, int port, int id) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
this.port = port;
this.id = id;
}
public void run() {
try {
startSignal.await();
doWork();
} catch (Throwable ex) {
ex.printStackTrace();
fail(id);
doneSignal.countDown();
}
}
void doWork() throws IOException {
HttpClientOptions options = new HttpClientOptions()
.setDefaultHost("localhost")
.setDefaultPort(port);
vertx.createHttpClient(options).getNow("/?id=" + id,
response -> response.bodyHandler((Handler<Buffer>) data -> {
if (!isOk(response.statusCode())) {
System.err.println("Bad error code for "
+ id + " got : " + response.statusCode());
fail(id);
return;
}
if (data.length() != id * 1000) {
System.err.println("Bad content for " + id + " got : " + data.length() + " " +
"bytes");
fail(id);
return;
}
success(id);
doneSignal.countDown();
}));
}
}
}