/* * #%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.http.HttpClientOptions; 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.exceptions.ExceptionMapper; import org.wisdom.api.http.*; import org.wisdom.api.router.Route; import org.wisdom.api.router.RouteBuilder; import org.wisdom.api.router.Router; import org.wisdom.framework.vertx.ssl.SSLServerContext; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.util.Collections; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Check the wisdom server behavior. * This class is listening for http requests on random port. */ public class VertxHttpServerTest extends VertxBaseTest { private WisdomVertxServer server; @After public void tearDown() { if (server != null) { server.stop(); server = null; } SSLServerContext.reset(); } @Test public void testServerStartSequence() throws InterruptedException, IOException { prepareServer(); server.start(); waitForStart(server); int port = server.httpPort(); URL url = new URL("http://localhost:" + port + "/test"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); assertThat(connection.getResponseCode()).isEqualTo(404); assertThat(server.hostname()).isEqualTo("localhost"); assertThat(port).isGreaterThan(9000); assertThat(server.httpsPort()).isEqualTo(-1); } @Test public void testOk() throws InterruptedException, IOException { Router router = prepareServer(); // Prepare the router with a controller Controller controller = new DefaultController() { @SuppressWarnings("unused") public Result index() { return ok("Alright"); } }; Route route = new RouteBuilder().route(HttpMethod.GET) .on("/") .to(controller, "index"); when(router.getRouteFor(anyString(), anyString(), any(Request.class))).thenReturn(route); server.start(); waitForStart(server); int port = server.httpPort(); URL url = new URL("http://localhost:" + port + "/"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); assertThat(connection.getResponseCode()).isEqualTo(200); String body = IOUtils.toString(connection.getInputStream()); assertThat(body).isEqualTo("Alright"); } @Test public void testInternalError() throws InterruptedException, IOException { Router router = prepareServer(); // Prepare the router with a controller Controller controller = new DefaultController() { @SuppressWarnings("unused") public Result index() throws IOException { throw new IOException("My bad"); } }; Route route = new RouteBuilder().route(HttpMethod.GET) .on("/") .to(controller, "index"); when(router.getRouteFor(anyString(), anyString(), any(Request.class))).thenReturn(route); server.start(); waitForStart(server); int port = server.httpPort(); URL url = new URL("http://localhost:" + port + "/"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); assertThat(connection.getResponseCode()).isEqualTo(500); } private Router prepareServer() { // 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("request.body.max.size", 100 * 1024)).thenReturn(100 * 1024); 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]); Router router = mock(Router.class); // Configure the server. server = new WisdomVertxServer(); server.configuration = configuration; server.accessor = new ServiceAccessor( null, configuration, router, getMockContentEngine(), null, null, Collections.<ExceptionMapper>emptyList() ); server.vertx = vertx; return router; } @Test public void testOkWithPlentyOfClients() throws InterruptedException, IOException { Router router = prepareServer(); // Prepare the router with a controller Controller controller = new DefaultController() { @SuppressWarnings("unused") public Result index() { return ok(context().parameter("id")); } }; Route route = new RouteBuilder().route(HttpMethod.GET) .on("/") .to(controller, "index"); when(router.getRouteFor(anyString(), anyString(), any(Request.class))).thenReturn(route); server.start(); waitForStart(server); // Now start bunch of clients int num = NUMBER_OF_CLIENTS; CountDownLatch startSignal = new CountDownLatch(1); CountDownLatch doneSignal = new CountDownLatch(num); int port = server.httpPort(); for (int i = 0; i < num; ++i) {// create and start threads executor.submit(new Client(startSignal, doneSignal, port, i)); } startSignal.countDown(); // let all threads proceed doneSignal.await(30, TimeUnit.SECONDS); // wait for all to finish assertThat(failure).isEmpty(); assertThat(success).hasSize(num); } @Test public void testOkWithPlentyOfClientsReadingJsonContent() throws InterruptedException, IOException { Router router = prepareServer(); // Prepare the router with a controller Controller controller = new DefaultController() { @SuppressWarnings("unused") public Result index() throws IOException { String content = IOUtils.toString(context().reader()); if (!content.equals(context().body())) { return badRequest("should be equal " + content + " / " + context().body()); } return ok(context().body()).json(); } }; Route route = new RouteBuilder().route(HttpMethod.POST) .on("/") .to(controller, "index"); when(router.getRouteFor(anyString(), anyString(), any(Request.class))).thenReturn(route); server.start(); waitForStart(server); // Now start bunch of clients int num = NUMBER_OF_CLIENTS; CountDownLatch startSignal = new CountDownLatch(1); CountDownLatch doneSignal = new CountDownLatch(num); for (int i = 0; i < num; ++i) { executor.submit(new PostClient(startSignal, doneSignal, i)); } startSignal.countDown(); // let all threads proceed doneSignal.await(30, TimeUnit.SECONDS); // wait for all to finish assertThat(failure).isEmpty(); assertThat(success).hasSize(num); } 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); } finally { doneSignal.countDown(); } } void doWork() throws IOException { URL url = new URL("http://localhost:" + port + "/?id=" + id); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); assertThat(connection.getResponseCode()).isEqualTo(200); String body = IOUtils.toString(connection.getInputStream()); if (!body.equals(String.valueOf(id))) { System.err.println("Wrong content for " + id); fail(id); return; } success(id); } } private class PostClient implements Runnable { private final CountDownLatch startSignal; private final CountDownLatch doneSignal; private final int id; PostClient(CountDownLatch startSignal, CountDownLatch doneSignal, int id) { this.startSignal = startSignal; this.doneSignal = doneSignal; this.id = id; } public void run() { try { startSignal.await(); doWork(); } catch (Throwable ex) { ex.printStackTrace(); fail(id); doneSignal.countDown(); } } void doWork() throws IOException { final String message = "{'id':" + id + "}"; HttpClientOptions options = new HttpClientOptions() .setDefaultHost("localhost") .setDefaultPort(server.httpPort()); vertx.createHttpClient(options) .post("/", response -> { response.bodyHandler( data -> { try { if (!isOk(response.statusCode())) { System.err.println("Bad response code for " + id + ", " + "got " + response.statusCode()); fail(id); return; } if (!message.equals(data.toString())) { System.err.println("Bad content for " + id + " - " + data); fail(id); return; } success(id); } catch (Exception e) { System.err.println(e.getMessage()); fail(id); } finally { doneSignal.countDown(); } } ); } ) .putHeader(HeaderNames.CONTENT_LENGTH, String.valueOf(message.length())) .putHeader(HeaderNames.CONTENT_TYPE, MimeTypes.JSON) .write(message) .end(); } } }