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
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
.
The method handleRequest
obtains an object that is also called handler
and further delegates to the executeHandler
method.
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.
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 relatedHandler
subclass, but their usage seems to be internal and specific to the handling ofEssentialAction
. 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 forHandler
. 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 functionHttpRequest => 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
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.
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:
-
EssentialFilter
, which represent cross-cutting concerns and influence all regular requests. They are documented in the documentation and our request needs to pass through those. -
Router
, which link requests to the controllers handling them. They have their own chapter in the documentation.
Chain of filters
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:
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 theHandler
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
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.
That looks like it could be helpful. Its sources are available inside the Play Framework and define some keys which we can inspect next:
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: 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:
- Controller method annotations
- Controller annotations
- Custom action creator
- Controller method is invoked
There are configuration options to manipulate this order:
play.http.actionComposition.controllerAnnotationsFirst = true
will cause the actions which are referenced by the annotations on the controller class to be executed before the ones referenced on the method itself. In the default flow, this switches steps 1 and 2.play.http.actionComposition.executeActionCreatorActionFirst = true
will result in the action creator to be called before the actions created from the annotations. In the default flow, this moves step 3 to the top.
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
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.
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}
whereext
can behtml
,js
,xml
, ortxt
.
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:
- Request is bound by Akka Http
- The router determines the controller and it’s method
- The filters are executed and may change the Http Headers
-
Action composition based on the controller
method.
Configuration can be used to change the order within this step.- Java-specific: Annotations on the controller method
- Java-specific: Annotations on the controller class
- Action from action creator
- The controller logic is executed
- As part of the controller logic, a Twirl template might be executed, e.g. for HTML.
- Action composition: Post processing
- Filters: Post processing
- 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.