diff --git a/play-framework/async-http/.g8/form/app/controllers/$model__Camel$Controller.java b/play-framework/async-http/.g8/form/app/controllers/$model__Camel$Controller.java new file mode 100644 index 0000000000..1ac4fe547d --- /dev/null +++ b/play-framework/async-http/.g8/form/app/controllers/$model__Camel$Controller.java @@ -0,0 +1,43 @@ +package controllers; + +import play.data.Form; +import play.data.FormFactory; +import play.mvc.Controller; +import play.mvc.Result; + +import javax.inject.Inject; + +// Add the following to conf/routes +/* +GET /$model;format="camel"$ controllers.$model;format="Camel"$Controller.$model;format="camel"$Get +POST /$model;format="camel"$ controllers.$model;format="Camel"$Controller.$model;format="camel"$Post +*/ + +/** + * $model;format="Camel"$ form controller for Play Java + */ +public class $model;format="Camel"$Controller extends Controller { + + private final Form<$model;format="Camel"$Data> $model;format="camel"$Form; + + @Inject + public $model;format="Camel"$Controller(FormFactory formFactory) { + this.$model;format="camel"$Form = formFactory.form($model;format="Camel"$Data.class); + } + + public Result $model;format="camel"$Get() { + return ok(views.html.$model;format="camel"$.form.render($model;format="camel"$Form)); + } + + public Result $model;format="camel"$Post() { + Form<$model;format="Camel"$Data> boundForm = $model;format="camel"$Form.bindFromRequest(); + if (boundForm.hasErrors()) { + return badRequest(views.html.$model;format="camel"$.form.render(boundForm)); + } else { + $model;format="Camel"$Data $model;format="camel"$ = boundForm.get(); + flash("success", "$model;format="Camel"$ " + $model;format="camel"$); + return redirect(routes.$model;format="Camel"$Controller.$model;format="camel"$Get()); + } + } + +} diff --git a/play-framework/async-http/.g8/form/app/controllers/$model__Camel$Data.java b/play-framework/async-http/.g8/form/app/controllers/$model__Camel$Data.java new file mode 100644 index 0000000000..50dc06f478 --- /dev/null +++ b/play-framework/async-http/.g8/form/app/controllers/$model__Camel$Data.java @@ -0,0 +1,37 @@ +package controllers; + +import play.data.validation.Constraints; + +public class $model;format="Camel"$Data { + + @Constraints.Required + private String name; + + @Constraints.Required + private Integer age; + + public $model;format="Camel"$Data() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + @Override + public String toString() { + return String.format("$model;format="Camel"$Data(%s, %s)", name, age); + } + +} diff --git a/play-framework/async-http/.g8/form/app/views/$model__camel$/form.scala.html b/play-framework/async-http/.g8/form/app/views/$model__camel$/form.scala.html new file mode 100644 index 0000000000..7bf9fd5427 --- /dev/null +++ b/play-framework/async-http/.g8/form/app/views/$model__camel$/form.scala.html @@ -0,0 +1,12 @@ +@($model;format="camel"$Form: Form[$model;format="Camel"$Data]) + +

$model;format="camel"$ form

+ +@flash.getOrDefault("success", "") + +@helper.form(action = routes.$model;format="Camel"$Controller.$model;format="camel"$Post()) { + @helper.CSRF.formField + @helper.inputText($model;format="camel"$Form("name")) + @helper.inputText($model;format="camel"$Form("age")) + +} diff --git a/play-framework/async-http/.g8/form/default.properties b/play-framework/async-http/.g8/form/default.properties new file mode 100644 index 0000000000..32090f30cb --- /dev/null +++ b/play-framework/async-http/.g8/form/default.properties @@ -0,0 +1,2 @@ +description = Generates a Controller with form handling +model = user diff --git a/play-framework/async-http/.g8/form/generated-test/README.md b/play-framework/async-http/.g8/form/generated-test/README.md new file mode 100644 index 0000000000..db01c87f30 --- /dev/null +++ b/play-framework/async-http/.g8/form/generated-test/README.md @@ -0,0 +1 @@ +Temporary file until g8-scaffold will generate "test" directory diff --git a/play-framework/async-http/.g8/form/generated-test/controllers/$model__Camel$ControllerTest.java b/play-framework/async-http/.g8/form/generated-test/controllers/$model__Camel$ControllerTest.java new file mode 100644 index 0000000000..7cdb87068b --- /dev/null +++ b/play-framework/async-http/.g8/form/generated-test/controllers/$model__Camel$ControllerTest.java @@ -0,0 +1,50 @@ +package controllers; + +import org.junit.Test; +import play.Application; +import play.filters.csrf.*; +import play.inject.guice.GuiceApplicationBuilder; +import play.mvc.*; +import play.test.WithApplication; + +import java.util.HashMap; + +import static org.junit.Assert.assertEquals; +import static play.mvc.Http.RequestBuilder; +import static play.mvc.Http.Status.OK; +import static play.test.Helpers.*; +import static play.api.test.CSRFTokenHelper.*; + +public class $model;format="Camel"$ControllerTest extends WithApplication { + + @Override + protected Application provideApplication() { + return new GuiceApplicationBuilder().build(); + } + + @Test + public void test$model;format="Camel"$Get() { + RequestBuilder request = new RequestBuilder() + .method(GET) + .uri("/$model;format="camel"$"); + + Result result = route(app, request); + assertEquals(OK, result.status()); + } + + @Test + public void test$model;format="Camel"$Post() { + HashMap formData = new HashMap<>(); + formData.put("name", "play"); + formData.put("age", "4"); + RequestBuilder request = addCSRFToken(new RequestBuilder() + .header(Http.HeaderNames.HOST, "localhost") + .method(POST) + .bodyForm(formData) + .uri("/$model;format="camel"$")); + + Result result = route(app, request); + assertEquals(SEE_OTHER, result.status()); + } + +} diff --git a/play-framework/async-http/.gitignore b/play-framework/async-http/.gitignore new file mode 100644 index 0000000000..eb372fc719 --- /dev/null +++ b/play-framework/async-http/.gitignore @@ -0,0 +1,8 @@ +logs +target +/.idea +/.idea_modules +/.classpath +/.project +/.settings +/RUNNING_PID diff --git a/play-framework/async-http/app/controllers/HomeController.java b/play-framework/async-http/app/controllers/HomeController.java new file mode 100644 index 0000000000..5c791dcd22 --- /dev/null +++ b/play-framework/async-http/app/controllers/HomeController.java @@ -0,0 +1,38 @@ +package controllers; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import java.util.Collections; +import java.util.Map; +import play.mvc.Controller; +import play.mvc.Http; +import play.mvc.Result; + +/** + * This controller contains an action to handle HTTP requests to the application's home page. + */ +public class HomeController extends Controller { + + /** + * An action that renders an HTML page with a welcome message. The configuration in the + * routes file means that this method will be called when the application receives + * a + * GET request with a path of /. + */ + public Result index(Http.Request request) throws JsonProcessingException { + return ok(printStats(request)); + } + + private String printStats(Http.Request request) throws JsonProcessingException { + Map stringMap = request.body() + .asFormUrlEncoded(); + Map map = ImmutableMap.of( + "Result", "ok", + "GetParams", request.queryString(), + "PostParams", stringMap == null ? Collections.emptyMap() : stringMap, + "Headers", request.getHeaders().toMap() + ); + return new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(map); + } +} diff --git a/play-framework/async-http/app/views/index.scala.html b/play-framework/async-http/app/views/index.scala.html new file mode 100644 index 0000000000..68d37fb1d4 --- /dev/null +++ b/play-framework/async-http/app/views/index.scala.html @@ -0,0 +1,5 @@ +@() + +@main("Welcome to Play") { +

Welcome to Play!

+} diff --git a/play-framework/async-http/app/views/main.scala.html b/play-framework/async-http/app/views/main.scala.html new file mode 100644 index 0000000000..c5f755f236 --- /dev/null +++ b/play-framework/async-http/app/views/main.scala.html @@ -0,0 +1,24 @@ +@* + * This template is called from the `index` template. This template + * handles the rendering of the page header and body tags. It takes + * two arguments, a `String` for the title of the page and an `Html` + * object to insert into the body of the page. + *@ +@(title: String)(content: Html) + + + + + @* Here's where we render the page title `String`. *@ + @title + + + + + @* And here's where we render the `Html` object containing + * the page content. *@ + @content + + + + diff --git a/play-framework/async-http/build.sbt b/play-framework/async-http/build.sbt new file mode 100644 index 0000000000..eea47ffe88 --- /dev/null +++ b/play-framework/async-http/build.sbt @@ -0,0 +1,12 @@ +name := """async""" +organization := "com.example" + +version := "1.0-SNAPSHOT" + +lazy val root = (project in file(".")).enablePlugins(PlayJava) + +scalaVersion := "2.13.1" + +// comment out the original line +libraryDependencies += guice +libraryDependencies += javaWs diff --git a/play-framework/async-http/conf/application.conf b/play-framework/async-http/conf/application.conf new file mode 100644 index 0000000000..492f37fed7 --- /dev/null +++ b/play-framework/async-http/conf/application.conf @@ -0,0 +1,11 @@ +# This is the main configuration file for the application. +# https://www.playframework.com/documentation/latest/ConfigFile +play.ws.followRedirects=false +play.ws.useragent=MyPlayApplication +play.ws.compressionEnabled=true +# time to wait for the connection to be established +play.ws.timeout.connection=30.seconds +# time to wait for data after the connection is open +play.ws.timeout.idle=30.seconds +# max time available to complete the request +play.ws.timeout.request=300.seconds diff --git a/play-framework/async-http/conf/logback.xml b/play-framework/async-http/conf/logback.xml new file mode 100644 index 0000000000..55441d39e2 --- /dev/null +++ b/play-framework/async-http/conf/logback.xml @@ -0,0 +1,36 @@ + + + + + + + ${application.home:-.}/logs/application.log + + %date [%level] from %logger in %thread - %message%n%xException + + + + + + %coloredLevel %logger{15} - %message%n%xException{10} + + + + + + + + + + + + + + + + + + + + + diff --git a/play-framework/async-http/conf/routes b/play-framework/async-http/conf/routes new file mode 100644 index 0000000000..4f5162a8e7 --- /dev/null +++ b/play-framework/async-http/conf/routes @@ -0,0 +1,10 @@ +# Routes +# This file defines all application routes (Higher priority routes first) +# ~~~~ + +# An example controller showing a sample home page +GET / controllers.HomeController.index(request: Request) +POST / controllers.HomeController.index(request: Request) + +# Map static resources from the /public folder to the /assets URL path +GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) diff --git a/play-framework/async-http/project/build.properties b/play-framework/async-http/project/build.properties new file mode 100644 index 0000000000..6adcdc753f --- /dev/null +++ b/play-framework/async-http/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.3.3 diff --git a/play-framework/async-http/project/plugins.sbt b/play-framework/async-http/project/plugins.sbt new file mode 100644 index 0000000000..1c8c62a0d5 --- /dev/null +++ b/play-framework/async-http/project/plugins.sbt @@ -0,0 +1,7 @@ +// The Play plugin +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.3") + +// Defines scaffolding (found under .g8 folder) +// http://www.foundweekends.org/giter8/scaffolding.html +// sbt "g8Scaffold form" +addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.11.0") diff --git a/play-framework/async-http/public/images/favicon.png b/play-framework/async-http/public/images/favicon.png new file mode 100644 index 0000000000..c7d92d2ae4 Binary files /dev/null and b/play-framework/async-http/public/images/favicon.png differ diff --git a/play-framework/async-http/public/javascripts/main.js b/play-framework/async-http/public/javascripts/main.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/play-framework/async-http/public/stylesheets/main.css b/play-framework/async-http/public/stylesheets/main.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/play-framework/async-http/test/controllers/HomeControllerTest.java b/play-framework/async-http/test/controllers/HomeControllerTest.java new file mode 100644 index 0000000000..a232dbfde0 --- /dev/null +++ b/play-framework/async-http/test/controllers/HomeControllerTest.java @@ -0,0 +1,232 @@ +package controllers; + +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.junit.Assert.assertEquals; +import static play.mvc.Http.Status.SERVICE_UNAVAILABLE; + +import akka.Done; +import akka.actor.ActorSystem; +import akka.stream.ActorMaterializer; +import akka.stream.javadsl.Sink; +import akka.util.ByteString; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.OptionalInt; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; +import org.apache.http.HttpStatus; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import play.Application; +import play.inject.guice.GuiceApplicationBuilder; +import play.libs.concurrent.Futures; +import play.libs.ws.WSClient; +import play.libs.ws.WSResponse; +import play.libs.ws.ahc.AhcCurlRequestLogger; +import play.mvc.Result; +import play.mvc.Results; +import play.test.WithServer; + +public class HomeControllerTest extends WithServer { + + private final Logger log = LoggerFactory.getLogger(HomeControllerTest.class); + private String url; + private int port; + + @Override + protected Application provideApplication() { + return new GuiceApplicationBuilder().build(); + } + + @Before + public void setup() { + OptionalInt optHttpsPort = testServer.getRunningHttpsPort(); + if (optHttpsPort.isPresent()) { + port = optHttpsPort.getAsInt(); + url = "https://localhost:" + port; + } else { + port = testServer.getRunningHttpPort() + .getAsInt(); + url = "http://localhost:" + port; + } + } + + @Test + public void givenASingleGetRequestWhenResponseThenBlockWithCompletableAndLog() + throws Exception { + WSClient ws = play.test.WSTestClient.newClient(port); + WSResponse wsResponse = ws.url(url) + .setRequestFilter(new AhcCurlRequestLogger()) + .addHeader("key", "value") + .addQueryParameter("num", "" + 1) + .get() + .toCompletableFuture() + .get(); + + log.debug("Thread#" + Thread.currentThread() + .getId() + " Request complete: Response code = " + + wsResponse.getStatus() + + " | Response: " + wsResponse.getBody() + " | Current Time:" + + System.currentTimeMillis()); + assert (HttpStatus.SC_OK == wsResponse.getStatus()); + } + + @Test + public void givenASingleGetRequestWhenResponseThenLog() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + WSClient ws = play.test.WSTestClient.newClient(port); + ws.url(url) + .setRequestFilter(new AhcCurlRequestLogger()) + .addHeader("key", "value") + .addQueryParameter("num", "" + 1) + .get() + .thenAccept(r -> { + log.debug("Thread#" + Thread.currentThread() + .getId() + " Request complete: Response code = " + + r.getStatus() + + " | Response: " + r.getBody() + " | Current Time:" + System.currentTimeMillis()); + latch.countDown(); + }); + + log.debug( + "Waiting for requests to be completed. Current Time: " + System.currentTimeMillis()); + latch.await(5, TimeUnit.SECONDS ); + assertEquals(0, latch.getCount()); + log.debug("All requests have been completed. Exiting test."); + } + + @Test + public void givenASinglePostRequestWhenResponseThenLog() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + WSClient ws = play.test.WSTestClient.newClient(port); + ws.url(url) + .setContentType("application/x-www-form-urlencoded") + .post("key1=value1&key2=value2") + .thenAccept(r -> { + log.debug("Thread#" + Thread.currentThread() + .getId() + " Request complete: Response code = " + + r.getStatus() + + " | Response: " + r.getBody() + " | Current Time:" + System.currentTimeMillis()); + latch.countDown(); + }); + + log.debug( + "Waiting for requests to be completed. Current Time: " + System.currentTimeMillis()); + latch.await(5, TimeUnit.SECONDS ); + assertEquals(0, latch.getCount()); + log.debug("All requests have been completed. Exiting test."); + } + + @Test + public void givenMultipleRequestsWhenResponseThenLog() throws Exception { + CountDownLatch latch = new CountDownLatch(100); + WSClient ws = play.test.WSTestClient.newClient(port); + IntStream.range(0, 100) + .parallel() + .forEach(num -> + ws.url(url) + .setRequestFilter(new AhcCurlRequestLogger()) + .addHeader("key", "value") + .addQueryParameter("num", "" + num) + .get() + .thenAccept(r -> { + log.debug( + "Thread#" + num + " Request complete: Response code = " + r.getStatus() + + " | Response: " + r.getBody() + " | Current Time:" + + System.currentTimeMillis()); + latch.countDown(); + }) + ); + + log.debug( + "Waiting for requests to be completed. Current Time: " + System.currentTimeMillis()); + latch.await(5, TimeUnit.SECONDS ); + assertEquals(0, latch.getCount()); + log.debug("All requests have been completed. Exiting test."); + } + + @Test + public void givenLongResponseWhenTimeoutThenHandle() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + WSClient ws = play.test.WSTestClient.newClient(port); + Futures futures = app.injector() + .instanceOf(Futures.class); + CompletionStage f = futures.timeout( + ws.url(url) + .setRequestTimeout(Duration.of(1, SECONDS)) + .get() + .thenApply(result -> { + try { + Thread.sleep(2000L); + return Results.ok(); + } catch (InterruptedException e) { + return Results.status( + SERVICE_UNAVAILABLE); + } + }), 1L, TimeUnit.SECONDS + ); + CompletionStage res = f.handleAsync((result, e) -> { + if (e != null) { + log.error("Exception thrown", e); + latch.countDown(); + return e.getCause(); + } else { + return result; + } + }); + res.thenAccept(result -> assertEquals(TimeoutException.class, result)); + + log.debug( + "Waiting for requests to be completed. Current Time: " + System.currentTimeMillis()); + latch.await(5, TimeUnit.SECONDS ); + assertEquals(0, latch.getCount()); + log.debug("All requests have been completed. Exiting test."); + } + + @Test + public void givenMultigigabyteResponseConsumeWithStreams() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + final ActorSystem system = ActorSystem.create(); + final ActorMaterializer materializer = ActorMaterializer.create(system); + final Path path = Files.createTempFile("tmp_", ".out"); + + WSClient ws = play.test.WSTestClient.newClient(port); + log.info("Starting test server on url: " + url); + ws.url(url) + .stream() + .thenAccept( + response -> { + try { + OutputStream outputStream = java.nio.file.Files.newOutputStream(path); + Sink> outputWriter = + Sink.foreach(bytes -> { + log.info("Reponse: " + bytes.utf8String()); + outputStream.write(bytes.toArray()); + }); + + response.getBodyAsSource() + .runWith(outputWriter, materializer); + + } catch (IOException e) { + log.error("An error happened while opening the output stream", e); + } + }) + .whenComplete((value, error) -> latch.countDown()); + + log.debug( + "Waiting for requests to be completed. Current Time: " + System.currentTimeMillis()); + latch.await(5, TimeUnit.SECONDS ); + assertEquals(0, latch.getCount()); + log.debug("All requests have been completed. Exiting test."); + } +}