Content negotiation

According to HTTP:

Content negotiation is the process of selecting the best representation for a given response when there are multiple representations available.

It can either be server-driven or agent-driven or a combination of both, which is called transparent negotiation. Play provides support for server-driven negotiations. This is handled by the rendering trait and is extended by the controller trait. The controller trait is the one where the controller objects in a Play app extend.

Let's look at the Rendering trait:

trait Rendering {

   object render { 

    //Tries to render the most acceptable result according to the request's Accept header value. 
    def apply(f: PartialFunction[MediaRange, Result])(implicit request: RequestHeader): Result = { 
      def _render(ms: Seq[MediaRange]): Result = ms match {
        case Nil => NotAcceptable 
        case Seq(m, ms @ _*) => 
          f.applyOrElse(m, (m: MediaRange) => _render(ms)) 
      } 

      // "If no Accept header field is present, then it is assumed that the client accepts all media types." 
      val result = 
        if (request.acceptedTypes.isEmpty) _render(Seq(new MediaRange("*", "*", Nil, None, Nil))) 
        else _render(request.acceptedTypes) 
      result.withHeaders(VARY -> ACCEPT) 
    } 

    /**Tries to render the most acceptable result according to the request's Accept header value. 
      * This function can be used if you want to do asynchronous processing in your render function. 
     */ 
    def async(f: PartialFunction[MediaRange, Future[Result]])(implicit request: RequestHeader): Future[Result] = { 
      def _render(ms: Seq[MediaRange]): Future[Result] = ms match { 
        case Nil => Future.successful(NotAcceptable) 
         case Seq(m, ms @ _*) => 
           f.applyOrElse(m, (m: MediaRange) => _render(ms)) 
      } 

      // "If no Accept header field is present, then it is assumed that the client accepts all media types." 
      val result = 
        if (request.acceptedTypes.isEmpty) _render(Seq(new MediaRange("*", "*", Nil, None, Nil))) 
        else _render(request.acceptedTypes) 
      result.map(_.withHeaders(VARY -> ACCEPT)) 
     } 
  } 
}

The _render method defined in the apply method calls the partial f function on the accept headers in the request. If f is not defined for the any of the accept headers, a response with status code 406 is forwarded. If it's not, the result of f for the first accept header for which f is defined, is returned.

Since the controller extends the rendering trait, we can use the render object within our Action definition. For example, we might have an Action, which gets the configuration in JSON and XML after reading it from a file with an XML format, depending on the accept headers in the request. Let's see how this is done:

def getConfig = Action {
    implicit request => 
      val xmlResponse: Node = <metadata> 
        <company>TinySensors</company> 
        <batch>md2907</batch> 
      </metadata> 

      val jsonResponse = Json.obj("metadata" -> Json.arr( 
        Json.obj("company" -> "TinySensors"), 
         Json.obj("batch" -> "md2907")) 
      ) 
      render { 
         case Accepts.Xml() => Ok(xmlResponse) 
        case Accepts.Json() => Ok(jsonResponse) 
      } 
  } 

In this snippet, Accepts.Xml() and Accepts.Json() are Play's helper methods that check to see if the request accepts the response of the application/xml and applicat ion/json types, respectively. There are currently four predefined accepts and these are tabulated here:

Request accept helper

Accept header value

XML

application/xml

JSON

application/json

HTML

text/html

JavaScript

text/javascript

This is facilitated by the RequestExtractors trait and the AcceptExtractors trait. RequestExtractors is also extended by the controller trait. Let's look at the extractor traits here:

trait RequestExtractors extends AcceptExtractors {

  //Convenient extractor allowing to apply two extractors. 
  object & { 
    def unapply(request: RequestHeader): Option[(RequestHeader, RequestHeader)] = Some((request, request)) 
  } 

}

//Define a set of extractors allowing to pattern match on the Accept HTTP header of a request 
trait AcceptExtractors {

  //Common extractors to check if a request accepts JSON, Html, etc. 
  object Accepts { 
    import play.api.http.MimeTypes 
    val Json = Accepting(MimeTypes.JSON) 
    val Html = Accepting(MimeTypes.HTML) 
    val Xml = Accepting(MimeTypes.XML) 
    val JavaScript = Accepting(MimeTypes.JAVASCRIPT) 
  } 

}

//Convenient class to generate extractors checking if a given mime type matches the Accept header of a request. 
case class Accepting(val mimeType: String) {
  def unapply(request: RequestHeader): Boolean = request.accepts(mimeType) 
  def unapply(mediaRange: play.api.http.MediaRange): Boolean = mediaRange.accepts(mimeType) 
}

From this code, all that we need to define a custom accepts is the value we would expect the request's accept header to have. For example, to define a helper for image/png, we use this code:

val AcceptsPNG = Accepting("image/png") 

We also notice that RequestExtractors has an & object, and we can use this when we wish to send the same response to multiple accept types. So, in the getConfig method shown in the preceding code, if the same response response is sent for application/json and text/javascript, we will modify it as follows:

def fooBar = Action { 
    implicit request => 
      val xmlResponse: Node = <metadata> 
        <company>TinySensors</company> 
        <batch>md2907</batch> 
      </metadata> 

      val jsonResponse = Json.obj("metadata" -> Json.arr( 
        Json.obj("company" -> "TinySensors"), 
        Json.obj("batch" -> "md2907")) 
      ) 
      
      render { 
        case Accepts.Xml() => Ok(xmlResponse) 
        case Accepts.Json() & Accepts.JavaScript() => Ok(jsonResponse) 
      }
  }

The render object can be used similarly when defining an asynchronous Action.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.35.247