diff --git a/app/controllers/Application.scala b/app/controllers/Application.scala index 7f2cbfed..bc1d2f10 100644 --- a/app/controllers/Application.scala +++ b/app/controllers/Application.scala @@ -164,7 +164,14 @@ class Application @Inject()( } def onHandlerNotFound(route: String) = Action { implicit request => - if (route.endsWith("/")) { + + if (route.startsWith("play-") && route.endsWith("-released") && !route.contains("-rc") && !route.contains("-m")) { + val version = route + .replace("play-", "") + .replace("-released", "") + .replace("-", ".") + MovedPermanently(s"https://github.com/playframework/playframework/releases/tag/$version") + } else if (route.endsWith("/")) { MovedPermanently("/" + request.path.take(request.path.length - 1).dropWhile(_ == '/')) } else { notFound diff --git a/app/controllers/Blog.scala b/app/controllers/Blog.scala new file mode 100644 index 00000000..ddc5bbce --- /dev/null +++ b/app/controllers/Blog.scala @@ -0,0 +1,43 @@ +package controllers + +import java.io.InputStream + +import javax.inject.Inject +import javax.inject.Singleton +import models._ +import org.apache.commons.io.IOUtils +import play.api._ +import play.api.cache.SyncCacheApi +import play.api.i18n.I18nSupport +import play.api.i18n.Lang +import play.api.mvc._ +import play.twirl.api.Html +import utils.Markdown +import views._ + +import scala.concurrent.ExecutionContext +import scala.concurrent.Future + +@Singleton +class Blog @Inject()( + components: ControllerComponents, +)(implicit ec: ExecutionContext, val reverseRouter: _root_.controllers.documentation.ReverseRouter) + extends AbstractController(components) + with Common + with I18nSupport { + + val blogName = "Play Framework Blog" + + def index() = Action { implicit request => + Ok(html.blog.index(blogName)) + } + + def graal() = Action { implicit request => + Ok(html.blog.graal(blogName, "Running Play on GraalVM")) + } + + def socketio() = Action { implicit request => + Ok(html.blog.socketio(blogName, "Play socket.io support")) + } + +} diff --git a/app/views/blog/graal.scala.html b/app/views/blog/graal.scala.html new file mode 100644 index 00000000..aea1a49a --- /dev/null +++ b/app/views/blog/graal.scala.html @@ -0,0 +1,120 @@ +@import controllers.documentation.ReverseRouter + @(blogName:String, title: String)(implicit req: RequestHeader, reverseRouter: ReverseRouter) +@main(title, blogName){ +
+
+

+ @blogName +

+
+
+
+
+
+

@title

+ +

+ Christian Schmitt, 10 MAY 2018 +

+ + +

On the 17th of April, Oracle Labs presented the community the first release cadence for their new universal virtual machine called GraalVM. Graal is a Polyglot VM that can run multiple languages and can interop between them without any overhead. In this blog post I will go into details what this means for Scala and especially for Play Framework and what runtime characteristics the new VM has, when running Play on top of it.

+

Graal currently comes in two flavors, one is the Community Edition which is open source and comes with the same license as a regular OpenJDK VM. Sadly at the moment the Community Edition is only available for Linux, which is good for production but mostly not enough for everyday development if you are not running Linux on your development machine.

+

There is also another edition called the Enterprise Edition which is not open source and you need to acquire a license to use it in production, but according to the docs it’s safe to use for development and evaluation. The Enterprise Edition is currently available for macOS and Linux, it has further benefits (comes with a smaller footprint and has more sandbox capabilities).

+

In the future the Graal team will probably present us with more options regarding the operating system. For our blog post we stick to the Community Edition on Linux.

+

Play Production Mode

+

Running Play or any Scala application on Graal is probably as easy as just switching to another Java VM. We will build the Play example project, via sbt-assembly and copy the production JAR to a regular server.

+

After downloading Graal and unpacking it, one can just run the application via $GRAAL_HOME/bin/java -Xms3G -Xmx3G -XX:+UseG1GC -jar play-scala-starter-example-assembly-1.0-SNAPSHOT.jar. Keep in mind for a production run, one would use a service manager or run the application inside an orchestration system like Kubernetes.

+

The Play application started without any problem and one could use curl to ensure it is running via curl http://graalserver:9000/ and it will print the Play “hello world page”.

+

“Performance” of Graal

+

After having the application running we can check how many requests/s it can serve via Graal, so we start up wrk with the following params: wrk -c100 -d1m -t2 http://graalserver:9000 and get an output like that (after a few runs):

+
Running 1m test @@ http://195.201.117.210:9000
+  2 threads and 100 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency    33.83ms   62.66ms   1.56s    94.36%
+    Req/Sec     2.15k   314.10     3.17k    68.92%
+  255800 requests in 1.00m, 1.76GB read
+Requests/sec:   4260.50
+Transfer/sec:     30.07MB
+

We can also compare that with a regular JVM which will output the following (after a few runs):

+
Running 1m test @@ http://195.201.117.210:9000
+  2 threads and 100 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency    38.41ms   70.39ms   1.79s    97.70%
+    Req/Sec     1.62k   219.60     3.10k    74.37%
+  193123 requests in 1.00m, 1.33GB read
+Requests/sec:   3216.56
+Transfer/sec:     22.70MB
+

As we can see Graal will be way faster compared to a regular JVM. The performance boost probably comes from better escape analysis. Keep in mind that the performance will be less on your own tests since you probably won’t run “hello world” on your systems.

+

AoT compilation

+

Currently Graal also has a way to compile a Java application to a single binary via native-image. However on Scala 2.12 native-image won’t work since Scala 2.12 relies on the invokedynamic bytecode instruction which as of now is not supported in SubstrateVM. But for reference I tried to use native-image on Scala 2.11.

+

To make that work I used sbt-assembly to create a standalone JAR that I can use as a reference to my native-image.

+

Sadly that also won’t work and will fail with the following error:

+
native-image --no-server -jar target/scala-2.11/play-scala-seed-assembly-1.0-SNAPSHOT.jar
+   classlist:  10,847.38 ms
+       (cap):   4,676.51 ms
+       setup:   5,769.91 ms
+warning: unknown locality of class Lplay/api/ApplicationLoader$JavaApplicationLoaderAdapter$1;, assuming class is not local. To remove the warning report an issue to the library or language author. The issue is caused by Lplay/api/ApplicationLoader$JavaApplicationLoaderAdapter$1; which is not following the naming convention.
+    analysis:   8,730.53 ms
+error: unsupported features in 3 methods
+Detailed message:
+Error: Must not have a FileDescriptor in the image heap.
+Trace:  object java.io.FileOutputStream
+        object java.io.BufferedOutputStream
+

Polyglot

+

One feature I was excited the most was support for Polyglot, which means that you can run other languages on top of the GraalVM. This is useful for interop with “native” languages or even JavaScript.

+

Sadly in the current form JavaScript can’t run NodeJS code from a Java Context which means that if I start my program with java my.package.Main and try to call into JavaScript that it can’t run Node. See: https://github.com/graalvm/graaljs/issues/2 for more details on the problem.

+

But what worked perfectly fine, was calling into native code. In the following example I just try to make a request to example.com via libcurl and print the response code inside my play controller.

+

For that to work we first need to create a C file:

+
#include <stdio.h>
+#include <curl/curl.h>
+
+long request() {
+    CURL *curl = curl_easy_init();
+    long response_code = -1;
+
+    if(curl) {
+      CURLcode res;
+      curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
+      res = curl_easy_perform(curl);
+      if(res == CURLE_OK) {
+        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
+      }
+      curl_easy_cleanup(curl);
+    }
+
+
+    return response_code;
+}
+

and turn it into bitcode via: clang -c -O1 -emit-llvm graal.c .

+

Than we need to add graal-sdk to our build.sbt via:

+
libraryDependencies += "org.graalvm" % "graal-sdk" % "1.0.0-rc1"
+

After that we can change one of our Play controllers to invoke it:

+
private val cpart = {
+  val polyglot = Context
+      .newBuilder()
+      .allowAllAccess(true)
+      .option("llvm.libraries", "/usr/lib/libcurl.dylib")
+      .build()
+  val source = Source
+      .newBuilder("llvm", new File("/Users/play/projects/scala/play-scala-seed/graal.bc"))
+      .build()
+  polyglot.eval(source)
+}
+
+def index() = Action { implicit request: Request[AnyContent] =>
+  val responseValue = cpart.getMember("request").execute()
+  val responseCode = responseValue.asLong()
+
+  Ok(s”$responseCode”)
+}
+

Creating a polyglot Context from Java and calling into another language currently works for the following languages (some which might be more experimental than others): JavaScript, all languages which can be turned into bitcode (C, C++, Rust, etc…), Python 3, R and Ruby.

+

Conclusion

+

In most cases Graal will actually run your Play application way faster than a regular JVM. Graal is especially good in running Scala code, since it has a way better escape analysis. However it can depend on your workload and what you do, so it’s probably a good idea to take a look at Graal by yourself.

+

If you are trying to interop with other languages Graal might also be a really good fit, since most languages can just be executed/run from a simple “Context” and Graal will also try his best to make the code as performant as possible.

+ + +
+
+} + diff --git a/app/views/blog/index.scala.html b/app/views/blog/index.scala.html new file mode 100644 index 00000000..3a549041 --- /dev/null +++ b/app/views/blog/index.scala.html @@ -0,0 +1,21 @@ +@import controllers.documentation.ReverseRouter + @(title: String)(implicit req: RequestHeader, reverseRouter: ReverseRouter) +@main(title, "blog"){ +
+
+

+ @title +

+
+
+
+
+
+ +
+
+} + diff --git a/app/views/blog/socketio.scala.html b/app/views/blog/socketio.scala.html new file mode 100644 index 00000000..c988e99c --- /dev/null +++ b/app/views/blog/socketio.scala.html @@ -0,0 +1,69 @@ +@import controllers.documentation.ReverseRouter +@(blogName:String,title: String)(implicit req: RequestHeader, reverseRouter: ReverseRouter) +@main( title, blogName){ +
+
+

+ @blogName +

+
+
+
+
+
+

@title

+ +

+ James Roper, 01 AUGUST 2017 +

+ +

The Play team are proud to announce official support for socket.io. We have created a library called play-socket.io which provides a complete engine.io and socket.io implementation, tested against the socket.io reference implementation client (that is, the official JavaScript socket.io client), and including a number of useful features such as backpressure and cluster support that the JavaScript implementations do not have.

+

Play has already proved itself to be apt at scaling to hundreds of thousands of connections per node, for example as demonstrated by LinkedIn, so having the straight forward multiplexing and event based API offered by the socket.io JavaScript client in combination with Play's powerful backend makes for a compelling technology stack for reactive applications.

+

Akka streams based

+

play-socket.io is built on Akka streams. Each socket.io namespace is handled by an Akka streams Flow, which takes at its inlet the stream of messages for that namespace coming from the client, and emits messages to go to the client.

+

One advantage of using Akka streams is that backpressure comes for free. This is an important feature for protecting servers from being overwhelmed with events. Without backpressure, there's no way for the server to tell the client to stop sending messages, so the server has to either process them, exhausting itself of CPU and other resources, or buffer them, and risk running out of memory. However play-socket.io will push back on the TCP connection when it can't keep up with rate of messages being sent from the client, preventing the client from sending any further messages. Likewise, backpressure from slow consuming clients gets pushed back to the source of Akka streams flows, ensuring a server will slow down its emission of messages and won't run out of memory buffering the messages that are yet to be consumed by the client.

+

Built-in clustering

+

Being built on Akka, play-socket.io does not need a sticky load balancer or any intelligent routing to serve socket.io endpoints. In most other socket.io implementations, if you have a socket.io endpoint served by a cluster of servers, you need to ensure that requests for the same engine.io session always get routed to the same node. With play-socket.io, requests can be handled by any node, and Akka clustering is used to ensure that they get routed to the right node. This allows the use of dumb, stateless load balancers, simplifying your deployment. The clustered chat example app in Scala and Java shows how to configure play-socket.io to work in a multi node environment, and even comes with a handy script to start 3 nodes behind an nginx load balancer to demonstrate the multi node setup at work.

+

Detailed documentation on using play-socket.io in a clustered setup can be found in the Scala and Java documentation.

+

Example code

+

Here's a minimal chat engine (similar to the official socket.io chat example) written in Play Scala:

+
import akka.stream.Materializer
+import akka.stream.scaladsl._
+import play.engineio.EngineIOController
+import play.socketio.scaladsl.SocketIO
+
+class ChatEngine(socketIO: SocketIO)(implicit mat: Materializer) {
+  import play.socketio.scaladsl.SocketIOEventCodec._
+
+  // codec to encode/codec chat message events to/from strings
+  val decoder = decodeByName {
+    case "chat message" => decodeJson[String]
+  }
+  val encoder = encodeByType[String] {
+    case _: String => "chat message" -> encodeJson[String]
+  }
+
+  // Merge/broadcast hub that each client will connect to
+  private val chatFlow = {
+    val (sink, source) = MergeHub.source[String]
+      .toMat(BroadcastHub.sink)(Keep.both).run
+    Flow.fromSinkAndSourceCoupled(sink, source)
+  }
+
+  val controller: EngineIOController = socketIO.builder
+    .addNamespace("/chat", decoder, encoder, chatFlow)
+    .createController()
+}
+
+

And then to ensure Play routes requests to the EngineIOController, add the following to your routes file:

+
GET     /socket.io/         play.engineio.EngineIOController.endpoint(transport)
+POST    /socket.io/         play.engineio.EngineIOController.endpoint(transport)
+

And that's all!

+

Documentation and samples

+

For installation instructions, comprehensive documentation and links to sample apps, see the documentation for Scala and Java. To contribute, visit the projects GitHub page.

+ + +
+
+} + diff --git a/app/views/main.scala.html b/app/views/main.scala.html index 84840069..7fdbcd27 100644 --- a/app/views/main.scala.html +++ b/app/views/main.scala.html @@ -60,7 +60,7 @@ Documentation Support Get Involved - Blog + Blog