We have talked about routes, and how to pass parameters to controllers. Let's now talk about what we can do with the controller.
The method defined in the route must return a play.api.mvc.Action
instance. The Action
type is a thin wrapper around the type Request[A] => Result
, where Request[A]
identifies an HTTP request and Result
is an HTTP response.
An HTTP response, as we saw in Chapter 7, Web APIs, is composed of:
The Play framework defines a play.api.mvc.Result
object that symbolizes a response. The object contains a header
attribute with the status code and the headers, and a body
attribute containing the body.
The simplest way to generate a Result
is to use one of the factory methods in play.api.mvc.Results
. We have already seen the Ok
method, which generates a response with status code 200:
def hello(name:String) = Action { Ok("hello, $name") }
Let's take a step back and open a Scala console so we can understand how this works:
$ activator console scala> import play.api.mvc._ import play.api.mvc._ scala> val res = Results.Ok("hello, world") res: play.api.mvc.Result = Result(200, Map(Content-Type -> text/plain; charset=utf-8)) scala> res.header.status Int = 200 scala> res.header.headers Map[String,String] = Map(Content-Type -> text/plain; charset=utf-8) scala> res.body play.api.libs.iteratee.Enumerator[Array[Byte]] = play.api.libs.iteratee.Enumerator$$anon$18@5fb83873
We can see how the Results.Ok(...)
creates a Result
object with status 200
and (in this case), a single header denoting the content type. The body is a bit more complicated: it is an enumerator that can be pushed onto the output stream when needed. The enumerator contains the argument passed to Ok
: "hello, world"
, in this case.
There are many factory methods in Results
for returning different status codes. Some of the more relevant ones are:
Action { Results.NotFound }
Action { Results.BadRequest("bad request") }
Action { Results.InternalServerError("error") }
Action { Results.Forbidden }
Action { Results.Redirect("/home") }
For a full list of Result
factories, consult the API documentation for Results (https://www.playframework.com/documentation/2.4.x/api/scala/index.html#play.api.mvc.Results).
We have, so far, been limiting ourselves to passing strings as the content of the Ok
result: Ok("hello, world")
. We are not, however, limited to passing strings. We can pass a JSON object:
scala> import play.api.libs.json._ import play.api.libs.json._ scala> val jsonObj = Json.obj("hello" -> "world") jsonObj: play.api.libs.json.JsObject = {"hello":"world"} scala> Results.Ok(jsonObj) play.api.mvc.Result = Result(200, Map(Content-Type -> application/json; charset=utf-8))
We will cover interacting with JSON in more detail when we start building the API. We can also pass HTML as the content. This is most commonly the case when returning a view:
scala> val htmlObj = views.html.index("hello") htmlObj: play.twirl.api.HtmlFormat.Appendable = <!DOCTYPE html> <html lang="en"> <head> ... scala> Results.Ok(htmlObj) play.api.mvc.Result = Result(200, Map(Content-Type -> text/html; charset=utf-8))
Note how the Content-Type
header is set based on the type of content passed to Ok
. The Ok
factory uses the Writeable
type class to convert its argument to the body of the response. Thus, any content type for which a Writeable
type class exists can be used as argument to Ok
. If you are unfamiliar with type classes, you might want to read the Looser coupling with type classes section in Chapter 5, Scala and SQL through JDBC.
We now know how to formulate (basic) responses. The other half of the equation is the HTTP request. Recall that an Action
is just a function mapping Request => Result
. We can access the request using:
def hello(name:String) = Action { request =>
...
}
One of the reasons for needing a reference to the request is to access parameters in the query string. Let's modify the Hello, <name>
example that we wrote earlier to, optionally, include a title in the query string. Thus, a URL could be formatted as /hello/Jim?title=Dr
. The request
instance exposes the getQueryString
method for accessing specific keys in the query string. This method returns Some[String]
if the key is present in the query, or None
otherwise. We can re-write our hello
controller as:
def hello(name:String) = Action { request => val title = request.getQueryString("title") val titleString = title.map { _ + " " }.getOrElse("") Ok(s"Hello, $titleString$name") }
Try this out by accessing the URL 127.0.0.1:9000/hello/Odersky?title=Dr
in your browser. The browser should display Hello, Dr Odersky
.
We have, so far, been concentrating on GET requests. These do not have a body. Other types of HTTP request, most commonly POST requests, do contain a body. Play lets the user pass body parsers when defining the action. The request body will be passed through the body parser, which will convert it from a byte stream to a Scala type. As a very simple example, let's define a new route that accepts POST requests:
POST /hello controllers.Application.helloPost
We will apply the predefined parse.text
body parser to the incoming request body. This converts the body of the request to a string. The helloPost
controller looks like:
def helloPost = Action(parse.text) { request =>
Ok("Hello. You told me: " + request.body)
}
You cannot test POST requests easily in the browser. You can use cURL instead. cURL is a command line utility for dispatching HTTP requests. It is installed by default on Mac OS and should be available via the package manager on Linux distributions. The following will send a POST request with "I think that Scala is great"
in the body:
$ curl --data "I think that Scala is great" --header "Content-type:text/plain" 127.0.0.1:9000/hello
This prints the following line to the terminal:
Hello. You told me: I think that Scala is great
There are several types of built-in body parsers:
parse.file(new File("filename.txt"))
will save the body to a file.parse.json
will parse the body as JSON (we will learn more about interacting with JSON in the next section).parse.xml
will parse the body as XML.parse.urlFormEncoded
will parse the body as returned by submitting an HTML form. The request.body
attribute is a Scala map from String
to Seq[String]
, mapping each form element to its value(s).For a full list of body parsers, the best source is the Scala API documentation for play.api.mvc.BodyParsers.parse
available at: https://www.playframework.com/documentation/2.5.x/api/scala/index.html#play.api.mvc.BodyParsers$parse$.