curvy path through the fields
A curvy path, shot by @jannesglas and published on Unsplash

The Play Framework is a web application framework for Scala and Java inspired by Ruby on Rails. Although I like the official documentation, I always found it lacking how requests runs through the framework. This article takes the reader on a journey tracing a request through the framework.

Disclaimer:: The core of the Play Framework is written in Scala. Starting from version 2.5, the documentation claims that the Java API “provides feature parity with the Scala APIs”. This means you should be able to use every feature with the Java API. As we are going to trace the request through the framework, we will also have a look at internal structures. Some of these structures may not be polished and or have no Java counterpart next to the Scala one. It also means that they may change in future versions.

The main goal of this article is to develop an understanding how requests pass through the framework. It is not the goal to provide a full guide how to customize it in detail, even though I may point out opportunities from time to time. Hopefully, future changes shouldn’t prevent the reader from gaining that understanding. Having said that, the guide was produced based on the 2.7 branch with the last commit beeing 85dc5bf. Bear in mind that some things may have already changed.

Bound by Akka Http

entrance into a garden of flowers
Entrance into a garden, shot by @belart84 and published on Unsplash

Play itself does not natively handle HTTP but delegates this task to libraries. Starting from version 2.6 Play uses Akka Http as a backend by default. Previously it was Netty which is optionally still available as an alternative backend. We stick to the default here and assume we are using Akka Http. As we want to trace the request from the very beginning, we need a bit of insight into the Akka Http framework; just enough to understand how HTTP requests enter the system and how Play takes over.

If you use Akka Http to start a server, you need to provide an Akka Http request handler, which is a function from HttpRequest to Async<HttpResponse>, where Async is either CompletionStage (Java) or Future (Scala). Play provides this function in a class that is – unsurprisingly – named AkkaHttpServer.

Http().bindAndHandleAsync(
  handler = handleRequest(_, connectionContext.isSecure),
  interface = context.config.address,
  port = port,
  connectionContext = connectionContext,
  settings = createServerSettings(port, connectionContext, secure)
)
Binding to requests to method handleRequest

The method handleRequest obtains an object that is also called handler and further delegates to the executeHandler method.

val (taggedRequestHeader, handler) = Server.getHandlerFor(debugInfoRequestHeader, tryApp)
val responseFuture = executeHandler(
  tryApp,
  decodedRequest,
  taggedRequestHeader,
  requestBodySource,
  handler
)
Determine handler and delegate further processing

This time however, the handler represents our very first semi-official Play API concept: Handlers.

Handler Interface

Handlers are Play’s top-level abstraction over how to process requests. I consider them to be semi-official because they are mentioned in another help topic (which we will discover later) which is only present in a current version for Scala, but not for Java—only for version 2.4.

/**
 * An Handler handles a request. Play understands several types of handlers,
 * for example `EssentialAction`s and `WebSocket`s.
 *
 * The `Handler` used to handle the request is controlled by `GlobalSetting`s's
 * `onRequestReceived` method. The default implementation of
 * `onRequestReceived` delegates to `onRouteRequest` which calls the default
 * `Router`.
 */
trait Handler
Complete definition of the handler interface

The definition by itself does not provide any information. However, the ScalaDoc points us towards the right direction: This class is likely intended to be used for pattern matching. This may be also a clue, why the interface is only documented in the Scala API, but not in the current Java one.

Regularly, one would limit the possible subclasses to a known set, but this was not done here. As a consequence, this interface is completely open for extension and does not provide any methods. This is a heavy burden for consumers of handlers which have to deal with it: they don’t provide any meaningful way to abstract over the concept without also looking at the interpretation. Luckily this interpreter is within the executeHandler method, at which we wanted to take a look at anyway. It lists the following, obviously implementation specific, subclasses:

  • EssentialAction is the most important one and is a fully official Play API! These are used for regular requests and – I would argue – are the most common variant which we will focus on.
    • Stage is a related Handler subclass, but their usage seems to be internal and specific to the handling of EssentialAction. They will reappear at a later stage but are mostly ignored for this article.
  • WebSocket are tasked with handling WebSocket connections and are the other variant mentioned in the ScalaDoc for Handler. They are not covered by this article, so I just refer to the Play documentation here.
  • AkkaHttpHandler is the last option and can be used when you want to opt out of the Play framework completely and handle the request directly in terms of the underlying Akka Http backend: as a function HttpRequest => Future[HttpResponse] within the API of Akka Http.

Requests other than WebSocket requests should go through the runAction method, which solely responds to the request with some bytes (potentially streaming) as ByteString. Request answered, case closed.

So is this it? Now we wonder though: what about all the MVC stuff like e.g. Controller that are mentioned in the documentation? These things are present as well, but not on the outer layer that we’ve dealt with up to this point. To enter an inner circle of Play, we need to take a closer look what’s inside an EssentialAction.

EssentialAction: A compound handler

Magnification of a gear
Magnification of a gear to take a closer look on more details from @pluyar on Unsplash

Our second official Play API class we encounter is the EssentialAction class. We received it from the call of Server#getHandlerFor as an implementation for the Handler interface. It pipes a request through the configured RequestFactory of our Application and then calls the configured HttpRequestHandler to determine an appropiate handler.

HttpRequestHandler

The HttpRequestHandler is the third semi-official API element. Oddly enough, it is only present in the Scala version of the current documentation. According to the documentation it is the lowest level intended for customization for a HTTP request:

Play provides a range of abstractions for routing requests to actions, providing routers and filters to allow most common needs. Sometimes however an application will have more advanced needs that aren’t met by Play’s abstractions. When this is the case,

[…] applications can provide custom implementations of Play’s lowest level HTTP pipeline API, the HttpRequestHandler.

Providing a custom HttpRequestHandler should be a last course of action.

This means that we have reached the official entry point of a request into Play territory! Everything we discovered prior to that was more or less an unofficial path to take you up to this point.

A HttpRequestHandler consumes the header of a request, possibly customizes it, and creates a Handler which processes the whole request body. The Java API represents this as an object of type HandlerForRequest, whereas the Scala API simply uses a tuple for this combination.

Plays' reasoning to strictly separate header and body

Request: Strictly split into header and body

Play tries to work on the RequestHeader without parsing the body for as long as possible. The reasoning is best described in the documentation itself:

The header is typically small - it can be safely buffered in memory, hence in Play it is modelled using the RequestHeader class. The body however can be potentially very long, and so is not buffered in memory, but rather is modelled as a stream.

This is why the RequestHeader is represented as its own class in addition to a Handler handling the whole request. Decide as much as possible based on the request header and just consume the body when the need for it arrives.

Stream processing a request

It even allows for a streaming processing of the request body, by accepting and responding chunks from the body where not all data must be present on the server. If we look at a terminal Handler in Play: the EssentialAction we can see, that it is expected to turn into an Accumulator which already works chunk wise. Its default implementation is the SinkAccumulator, which does exactly that by using Akka Streams.

Akka Streams is a library implementing reactive streams. It should be possible to construct another implementation using java.util.concurrent.Flow, which is also a reactive stream implementation, but native on the JVM. But there is nothing wrong about using Akka Streams as well.

As mentioned above, the HttpRequestHandler can be configured and swapped out, but let’s assume we are dealing with a vanilla implementation and take a look at the DefaultHttpRequestHandler. It is written in Scala with a Java compability layer. It comprises multiple parts, but we will focus on these two:

Chain of filters

2 persons holding coffee filters in their hands
2 persons creatively constructing a combined coffee filter, shot by @nate_dumlao and published on Unsplash

The interface of a HttpRequestHandler defines a single method. So let’s take a closer look at what the default implementation provides. The full code sample is approximately 70 lines in total. Luckily, most of it is spent on helper methods with abstractions like routeWithFallback and handling of the development mode, which we can omit for our purposes and focus on this part instead:

// 1. Query the router to get a handler

// 2. Resolve handlers that preprocess the request

// 3. Modify the handler to do filtering, if necessary

// 4. Again resolve any handlers that do preprocessing

val routedHandler = routeWithFallback(request)
val (preprocessedRequest, preprocessedHandler) = Handler.applyStages(request, routedHandler)
val filteredHandler = filterHandler(preprocessedRequest, preprocessedHandler)
val (preprocessedPreprocessedRequest, preprocessedFilteredHandler) = Handler.applyStages(preprocessedRequest, filteredHandler)
(preprocessedPreprocessedRequest, preprocessedFilteredHandler)
Routing and filtering invoking snippet

Here, the most relevant steps are 1 and 3, but let’s first get steps 2 and 4 out of the way.

As promised before, we will come back to the internal concept of a Stage. A Stage is a wrapper for a Handler and is intended for preprocessing. But because handlers are only used by pattern matching and provide no meaningful logic themselves, if something wants to preprocess the request, it either needs the interpreter to be aware of the wrapper type or they must be unwrapped before reaching it. The implementation above chose the second approach and step 1 and 3 are now unwrapping and executing the preprocessing steps.

  • Step 1 is calling a locally defined helper method, which finally delegates to the configured Router instance and is the source of the Handler we receive. It determines the controller method which will be the next step in our trace and will be covered by the next chapter.
  • Step 3 takes all EssentialFilter and prepends them to the request processing for EssentialActions. They are constructed last, but because they are prepended to the pipeline they will be processed at first.
    (Note: Filter won’t filter other requests, e.g. WebSockets)

Router: Linking general processing to controller logic

Multiple signs for directions
Signs routing travelles, shot by @d0cz on Unsplash

Routing in Play is usually done with a domain specific language: the routes file, unless you have explicitly opted out of this, e.g. by using a String Interpolating Router. In that case, you should already be familiar how requests are routed to the controller methods: you’ve written the code yourself!

Earlier, we saw that a Router instance was injected into our HttpRequestHandler object. This instance comes from a class file that was automatically generated by the build system. During a build step, the routes file is translated to Scala code and subsequently compiled by the Scala compiler. Let’s find out how by using the sbt shell. I really like sbt’s discoverability and I also think it helps to understand how the parts are glued together.

$ plugins
  […] play.sbt.routes.RoutesCompiler: enabled in <your project> […]
Finding relevant plugins within the sbt shell

That looks like it could be helpful. Its sources are available inside the Play Framework and define some keys which we can inspect next:

$ inspect playRoutes
  [info] Task: scala.collection.Seq[java.io.File]
  [info] Description:
  [info] 	Compile the routes files
  […]
  [info] Dependencies:
  [info] 	Compile / playRoutesGenerator
  [info] 	Compile / state
  [info] 	Compile / playRoutesTasks
  [info] 	Compile / playRoutes / streams
  [info] 	Compile / playRoutes / target
  […]

$ inspect playRoutesTasks
  [info] Task: scala.collection.Seq[play.routes.compiler.RoutesCompiler$RoutesCompilerTask]
  [info] Description:
  [info] 	The routes files to compile
  […]
  [info] Dependencies:
  [info] 	Compile / playRoutes / sources
  [info] 	Compile / playGenerateReverseRouter
  [info] 	Compile / settingsData
  [info] 	Compile / playRoutesImports
  [info] 	Compile / configuration
  [info] 	Compile / playAggregateReverseRoutes
  [info] 	Compile / playNamespaceReverseRouter

$ show playRoutes::source
  [info] * <path to project>/conf/routes

$ show playRoutes
  [info] * <path to project>/target/scala-2.12/routes/main/router/RoutesPrefix.scala
  [info] * <path to project>/target/scala-2.12/routes/main/controllers/routes.java
  [info] * <path to project>/target/scala-2.12/routes/main/controllers/ReverseRoutes.scala
  [info] * <path to project>/target/scala-2.12/routes/main/controllers/javascript/JavaScriptReverseRoutes.scala
  [info] * <path to project>/target/scala-2.12/routes/main/router/Routes.scala
Inspecting route specific build keys in the sbt shell

What we just learned is that there is a single source file for the routing: the conf/routes file. But there are multiple target files, which will be stored in target/scala-2.12/routes. How many depends on whether you enabled reverse routing, JavaScript routing, etc.

If you are a Java developer, the process of compiling sources from external files may be familiar from e.g. the Mavens Jaxb2 Plugin. It is also a plugin for the build system that takes some sources (the schema definition) and generates source code from them. This is the same principle, except that the sources are not XML and the target is Scala instead of Java.

I don’t recommend to look too closely at routes compiler, though. If you absolutely must, there is the parser which reads the routes file. And if you look at the generated router inside the target directory, you can see that it is similar with this Twirl template. I leave the rest, namely, how these two are intertwined, to the readers' imagination. (Personally, I am not a huge fan of using a textual template/HTML templating language to create source code.)

Action composition/Annotations

The router links the request from the framework to a method within a controller. But before the logic inside the controller is called, you can modify each request individually. Each controller method can call multiple actions, which are combined into a single one. This is why it’s called action composition.

This process differs heavily between the Scala and Java world. Action composition in Scala can be expressed on the language level as chaining of functions. On the other side, Java programmers are used to use annotations. Annotations need some code to interpret them. Consequently, this chapter focuses mainly on the Java side.

To see how annotations on controllers are wired to the calling router, we have to take a look at the generated router after all. All generated routers will inherit from a class called GeneratedRouter and within it, there is a method createInvoker with an implicitly passed parameter HandlerInvokerFactory[T]. If you are reading this chapter, chances are you are a Java user not familiar with Scala and its concept of implicit parameters. If so, please see the box below for a short introduction.

Scala: Short introduction about implicit parameters / type classes

Scala: Implicit parameters

Scala has the notion of implicit parameters, which are special parameters that,if absent at the call site, are filled in by the compiler. For the compiler to know which value it should provide there, the current scope needs to have an expression – explicitly marked for implicit resolution – available.

To resolve ambiguities, the compiler categorized possible expressions into scopes by certain rules and the nearest value is taken. This is always unique (otherwise it’s a compile error) and deterministic. Because values are scoped, libraries can place values intended for default usage in an outer scope, so that more concrete code can put values in a nearer scope, thus shading these values.

The HandlerInvokerFactory limits the values which are possible to use for your Java controller methods. The values are imported from the companion object of HandlerInvokerFactory:

  • play.mvc.Result
  • java.util.concurrent.CompletionStage<play.mvc.Result>
  • Function<play.mvc.Http.Request, play.mvc.Result>
  • Function<play.mvc.Http.Request, java.util.concurrent.CompletionStage<play.mvc.Result>>

This means that the Play Framework supports controller methods acting as these functions to be wired.

Side note: It should be possible to extend these types. As an example use case, you might want to support Vavr Futures instead of the Java API of CompletionStage and instead of always converting them to a CompletionStage for Play.

The HandlerInvokerFactory always wraps the action inside a JavaAction, which is itself a special case of an EssentialAction and thus a Handler. This is finally the place where the exact order of all configured actions is resolved and actions from annotations and from an configured action creator are melded together. The default order is the following:

  1. Controller method annotations
  2. Controller annotations
  3. Custom action creator
  4. Controller method is invoked

There are configuration options to manipulate this order:

Luckily, the documentation also describes a configuration option to enable debug logging for these, in case you ever wonder what the final order in a concrete call will be.

Twirl for serving HTML

Water coloring a sketch
An artist coloring a sketch, making it more livid. Shot by @nikarthur on Unsplash

If you are rendering HTML, chances are that you are using Twirl: Play’s default templating engine. These are Scala-esque template files which you can call from your controllers to render HTML.

Just like the router files, they are processed in the build and compiled to class files. We inspect them the same way we already did with routes files.

$ inspect twirlCompileTemplates
  [info] Task: scala.collection.Seq[java.io.File]
  [info] Description:
  [info] 	Compile twirl templates into scala source files
  [info] 	Compile / twirlTemplateImports
  [info] 	Compile / twirlCompileTemplates / sourceDirectories
  […]

  […]
$ show compile:twirlCompileTemplates::sourceDirectories
  [info] * <your project folder>/main/app

$ show compile:twirlCompileTemplates::includeFilter
  [info] PatternFilter(.*\Q.scala.\E.*)

$ show compile:twirlTemplateFormats
  [info] Map(
    html -> play.twirl.api.HtmlFormat,
    txt -> play.twirl.api.TxtFormat,
    xml -> play.twirl.api.XmlFormat,
    js -> play.twirl.api.JavaScriptFormat
  )

$ show compile:twirlCompileTemplates::target
  [info] <your project folder>/target/scala-2.12/twirl/main
Inspecting Twirl relevant build keys inside a sbt shell

This shows us that the compiler will compile everything within /main/app whose file name includes .scala. like index.scala.html. The result of the compilation is stored within the target folder in a separate folder named twirl. Once these templates have been generated, they are regular code that can be called from within your controller methods.

Twirl’s README also explains that:

Template files must be named {name}.scala.{ext} where ext can be html, js, xml, or txt.

This corresponds to the twirlTemplateFormats configured above. If you want to extend this, you would need a new Format[T], implement its interface and register it within the build using the corresponding file extension.

Finally, Twirl returns an instance depending on the file format. In the case of HtmlFormat (suffix: html), it’s an Html class. This will be passed inside a Result object, which is the final result of a controller method.

From that point on, the chain is rolled up and postprecessing may take place. Time for a summary.

Summary: What did we just ran through?

Here are the steps in the order they are processed for a request, assuming a regular request and default configuration. Notes in these stages refer to potential use cases that can be applied in these phases:

Visualization of the request flow with typical examples
Visualization of the request flow with typical examples
  1. Request is bound by Akka Http
  2. The router determines the controller and it’s method
  3. The filters are executed and may change the Http Headers
  4. Action composition based on the controller method.
    Configuration can be used to change the order within this step.
    1. Java-specific: Annotations on the controller method
    2. Java-specific: Annotations on the controller class
    3. Action from action creator
  5. The controller logic is executed
    1. As part of the controller logic, a Twirl template might be executed, e.g. for HTML.
  6. Action composition: Post processing
  7. Filters: Post processing
  8. Akka Http: Framework/Backend post processing (e.g. Cookie Header)

Every filter/action could do some postprocessing of the result, which means that each requests now also passes these again in reverse order. A good example for this is the gzip encoding filter, which does nothing on the forward way but to delegate to the next filter/action. It’s carrying out its main purpose during postprocessing by taking the result and encoding it to gzip.

Further topics

During tracing the request through the framework, I had to make several choices about which paths were worth tracing and presenting here. Maybe you disagree with my choices and want to learn about the areas I omitted as well? Here are a few topics which I consider to be interesting for futher reading:

  • Web Sockets are fully supported by Play and – other than the EssentialAction – the other main case to be handled. They diverge at an very early stage from regular requests, so I didn’t cover them in this article.
  • Build tools used by Play. Play uses the build to transform text files into executable functions processing requests (like the router and the Twirl templates). Each of these build tools have their own semantics.
    • sbt: I’ve used sbt because it was the first supported build framework and I like its discoverability. I tried to show how the build tool can be used to inspect the build to discover where the things are transformed and how certain configuration options influence these. These things can act as a staring point. If you are interested, I recommend to check the documentation, especially on scopes.
    • Gradle was not covered at all here, but can be used to run Play as well.
  • Plugins that take advantage of the request processing structure. Authorization would be a typical use case. Some example libraries doing this are: Silhouette, Pac4J or more lightweight solutions like JWT-Scala
  • If you want to understand all details within the internal core, which aren’t exposed or just want to learn a great language, it might be worth to learn a bit of Scala.

I hope you consider this article useful to understand the basic lifecycle of a request in the Play Framework, or even raise interest in the Play Framework.

Special thanks to Lars Hupel, who proof read this blog post through the time and convinced me to actually publish it.

TAGS