Controllers in Play are responsible for handling a request and returning a response. Here is a basic Java controller (which would live in app/controllers/FooController.java):
package controllers;
import play.mvc.Result;
import play.mvc.Controller;
public class FooController extends Controller {
public Result get() {
return ok("Hello Foo");
}
}
By extending the base play.mvc.Controller class, we pull in some convenience methods, but doing so is not required. The controller instance is created by using Guice, a dependency injection framework. The get() method returns a play.mvc.Result, which represents the HTTP response. In this case, the response is a status code "200 OK" response because the ok helper was used to set that status code. There are many other helpers like notFound and badRequest that wrap the general purpose play.mvc.Status API. The response body in this example is just a String, but it could also be HTML content, a stream, a file, etc.
The corresponding Scala controller is quite similar (and would live in app/controllers/FooController.scala):
package controllers
import javax.inject.Inject
import play.api.mvc.BaseController
import play.api.mvc.ControllerComponents
class FooController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {
def get = Action {
Ok("Hello foo")
}
}
The primary difference with this Scala example is that the controller returns an Action which holds a function that takes a request (optionally specified) and returns a response. Just as the Java controller, by default, we use dependency injection to create an instance of the controller. It can be provided by Guice or it can use compile-time Dependency Injection. The controllerComponents instance injected has a number of built-in utilities so that we can have simpler controllers, but as long as you have an Action, it will work.
The Controller class (in the Java API) has some convenience methods to interact with the other parts of the request and response:
ctx() |
Returns the HTTP context, which can be used to retrieve and store data relevant to the current request. |
flash(), flash(String key), and flash(String key, String value) |
Can be used to access a state that is only available for a single request after the current one (this is useful for displaying messages after a redirect). |
session(), session(String key), session(String key, String value) |
Can be used to access the session state which is backed by a cookie. |
request() |
Return the current HTTP request object, which can be used for reading HTTP request headers, body, and any other data related to the request. |
response() |
Returns the current HTTP response object, which can be used to set cookies, HTTP headers, etc. |
In the Scala API, these types of operations are done either on the Action function’s optional request parameter or on the Result, for example:
def get = Action { request =>
Ok("Hello")
.withHeaders("Foo" -> "bar")
.withSession("SessionFoo" -> "The-value")
}
There are other response helper methods that you can use depending on the HTTP Status code you need to be returned:
200 OK |
ok |
Ok |
201 Created |
created() |
Created |
301 Moved Permanently |
movedPermanently |
MovedPermanently |
302 Found |
found |
Found |
303 See Other |
seeOther or redirect |
SeeOther |
307 Temporary Redirect |
temporaryRedirect |
TemporaryRedirect |
308 Permanent Redirect |
permanentRedirect |
PermanentRedirect |
404 Not Found |
notFound |
NotFound |
406 Not Acceptable |
notAcceptable |
NotAcceptable |
415 Unsupported Media Type |
unsupportedMediaType |
UnsupportedMediaType |
500 Internal Server Error |
internalServerError |
InternalServerError |
Any status |
status(413, “Oops") |
Status(413)("Oops") |
Controllers in Play are internally asynchronous and non-blocking, and there is support for using non-blocking APIs in a more idiomatic way. For example, you can just return a Result directly, as shown before, or your actions can return CompletableFuture<Result>:
public CompletableFuture<Result> getAsync() {
// Some async API call
CompletableFuture<String> asyncApiResponse = someAsyncApi();
return asyncApiResponse
.thenApply(value -> ok("Api Result: " + value));
}
In Scala, there is Action.async to better integrate with APIs returning a Future:
def getAsync = Action.async {
val asyncApiResponse: Future[String] = someAsyncApi()
asyncApiResponse.map(value => Ok("Api Result: " + value))
}
Interceptors can be added to controllers in order to add security, logging, caching, and other custom behaviors. This is called Action Composition. In Play’s Java API, annotations are used to add the interceptors. In Scala, Action Composition is achieved through functional composition.
Controllers go much deeper than the typical request and response handling. For instance, a controller can return a stream or it can be used to setup a push connection (Comet, EventSource, WebSocket, etc). Controllers can also handle more than just HTML; they can be used for JSON, binary files, or any content type using custom Body Parsers.
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}
{{ parent.urlSource.name }}