Handling content negotiation

The same HTTP POST endpoints can process the JSON content as well as the URL-encoded content; however, GET endpoints, which previously returned JSON content, now only return HTML content. Is it possible to return both the JSON and HTML content from the same GET endpoints (similarly to what has been done for POST endpoints)?

The answer is yes, and this HTTP feature is named content negotiation. The word negotiation comes from the fact that HTTP clients inform servers of which versions of a resource they would rather get (according to their capabilities). They do this by specifying HTTP request headers starting with Accept. For instance, your web browser usually sends, along with each request, an Accept-Language header containing your preferred languages. This gives the server the opportunity to return a version of the document in a language that fits best your preferences. The same applies to the result content types, which are driven by the Accept header. For instance, my web browser sends the following header when I click on a hyperlink:

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

This means that it prefers HTML or XML content (q=0.9) but also accepts everything (*/*;q=0.8).

You can handle this header in order to serve HTML content to web browsers, but also JSON content to clients preferring this content type. This can be achieved as follows:

  val list = Action { implicit request =>
    val items = shop.list()
    render {
      case Accepts.Html() => Ok(views.html.list(items))
      case Accepts.Json() => Ok(Json.toJson(items))
    }
  }

The render function takes a partial function as a parameter. This one tells which content types your server supports and their corresponding results. The render function starts by reading the Accept header of the current request. Then, for each content type set in the header, it tests whether this content type is supported by your server. Finally, it chooses the content type that is supported by your server and has the highest q value. If none of the content types supported by your server are accepted by the client, a response with the 406 status (Not Acceptable) is returned.

Note that if a client defines no Accept header, then the server can consider that it accepts all content types, so the framework will pick the first one in your render call. That's why I put the Accepts.Html() line first in the preceding code; the default is to serve HTML, except for clients explicitly telling that they prefer JSON over HTML.

At the time of writing this, there is no direct equivalent in the Java API of the framework. However, it is easy to define some helper functions similar to the Scala content negotiation API so that your Items.list action looks like the following:

public static Result list() {
  return render(
    version(MimeTypes.HTML, () -> ok(views.html.list.render(shop.list()))),
    version(MimeTypes.JSON, () -> ok(Json.toJson(shop.list())))
  );
}

The render and version functions are imported from this Render.java file:

import play.api.http.MediaRange;
import play.mvc.Controller;
import play.mvc.Result;
import java.util.function.Supplier;

public class Render extends Controller {

  public static Result render(Version... versions) {
    List<MediaRange> acceptedTypes = request().acceptedTypes();
    if (acceptedTypes.isEmpty() && versions.length > 0) {
      return versions[0].resultThunk.get();
    }
    for (MediaRange mediaRange : acceptedTypes) {
      for (Version version : versions) {
        if (mediaRange.accepts(version.mimeType)) {
          return version.resultThunk.get();
        }
      }
    }
    return status(NOT_ACCEPTABLE);
  }

  public static class Version {
    public final String mimeType;
    public final Supplier<Result> resultThunk;

    public Version(String mimeType, Supplier<Result> resultThunk) {
      this.mimeType = mimeType;
      this.resultThunk = resultThunk;
    }
  }

  public static Version version(String mimeType, Supplier<Result> resThunk) {
    return new Version(mimeType, resThunk);
  }

}

The render method takes a list of supported versions of your document, each one being associated with a MIME type, and tries to find a media range accepted by the current request that matches one of your versions. It returns the first matching version or a 406 status (Not Acceptable) if none of your versions are accepted by the client.

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

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