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