Play – Controller et les variantes de render() (JSON, XML, fichier, …)

Dans l’article précédent, on a vu comment gérer les paramètres « en entrée » d’un Controller, grâce au mapping manuel ou automatique effectué par Play:

Play – Controller et mapping des paramètres de requête HTTP (GET, POST, File, …)

Mais pour l’instant, vous n’avez rien en sortie. Le retour de votre requête HTTP est pour l’instant vide.

Pour pouvoir produire du contenu en sortie, vous allez utiliser une méthode de Play nommée « render(…) ». Celle-ci vous permettra de demander le rendu d’une View. Mais Play ne sert pas uniquement à servir des pages HTML. Vous pouvez bien sûr l’utiliser pour réaliser votre API, qui va retourner du JSON. Ou pour servir des fichiers, renvoyer du XML, …

Pour cela, Play expose d’autres méthodes, des variantes de la méthode render() qui seront plus spécialisées mais qui vous éviterons des lignes de code inutiles. La méthode renderJSON(…) par exemple va prendre en entrée un objet Java ou une List et s’occuper de la sérialiser en JSON.

Pourquoi render(…) et pas return?

Dans une méthode « classique », on a des paramètres en entrée et on renvoie (ou pas) des objets en sortie avec une instruction return. Dans le cas de Play, la méthode render (ou une de ses variantes) va en fait émettre un objet de type Result.

Quand je dis émettre, Play va en fait lancer une exception Java contenant cet objet Result. L’exception sera récupérée plus haut par l’initiateur de la requête et renvoyer le contenu en tant que réponse. Vous trouvez que lancer une exception c’est moche? Perso je trouve cela plutôt malin. Dans la pratique, c’est transparent.

Puisqu’un render va lancer une exception, cela signifie que tout ce qui se situe après la méthode render ne sera pas exécuté:

public static void show(Long id) {
    Client client = Client.findById(id);
    render(client);
    System.out.println("Code jamais exécuté");
}

Ce mécanisme permet d’être plus souple. Play va notamment pouvoir renvoyer correctement les types MIME des données renvoyées, ce qui aurait été difficile à faire avec une instruction return (trop limitée).

Renvoyer du texte simple (text plain)

La méthode renderText(…) permet de renvoyer du contenu texte directement dans la requête HTTP:

public static void countUnreadMessages() {
    Integer unreadMessages = MessagesBox.countUnreadMessages();
    renderText(unreadMessages);
}

Renvoyer du contenu au format JSON

Pour votre API publique, vous allez sûrement utiliser du JSON, qui est maintenant devenu le standard du web (notamment grâce au JSONP). Pour vous faciliter la vie, Play vous expose une méthode renderJSON(…) qui prend en paramètre un objet. Celui-ci sera automatiquement sérialisé grâce aux classes GSON (parser / stringifier de chez Google).:

public static void getUnreadMessages() {
    List<Message> unreadMessages = MessagesBox.unreadMessages();
    renderJSON(unreadMessages);
}

Renvoyer du contenu XML

De la même manière, Play vous permet de renvoyer directement du contenu XML (type MIME text/xml). Vous disposez donc d’une méthode renderXml(…) qui prend au choix:

  • Une chaîne au format XML
  • Un objet de type org.w3c.Document
  • Un objet quelconque qui sera sérialisé en XML par XStream
public static void countUnreadMessages() {
    Integer unreadMessages = MessagesBox.countUnreadMessages();
    renderXml("<unreadmessages>"+unreadMessages+"</unreadmessages>");
}

Renvoyer du contenu binaire (fichiers)

Play dispose d’une méthode renderBinary(…) vous permettant de renvoyer du binaire, par exemple un BLOB de votre BDD contenant une image. Comme ce contenu binaire peut être de n’importe quel type MIME, vous devrez le préciser vous-même à Play grâce à la méthode response.setContentTypeIfNotSet(…).

Petit exemple qui renvoie un BLOB grâce à JPA depuis la base de données:

public static void userPhoto(long id) {
   final User user = User.findById(id);
   response.setContentTypeIfNotSet(user.photo.type());
   java.io.InputStream binaryData = user.photo.get();
   renderBinary(binaryData);
}

Renvoyer un fichier à télécharger

La méthode renderBinary() possède aussi une variante permettant d’indiquer que le fichier doit être traité par le navigateur comme un fichier à télécharger en mettant settant le header « Content-Disposition » qui va indiquer le nom du fichier à télécharger. Ce nom de fichier doit être passé en tant que 2ème paramètre:

renderBinary(binaryData, user.photoFileName);

Pour les curieux (under the hood!)

Si vous êtes un peu curieux de voir le fonctionnement du mécanisme de render de Play, voici le contenu de la méthode renderText():

    /**
     * Return a 200 OK text/plain response
     * @param text The response content
     */
    protected static void renderText(Object text) {
        throw new RenderText(text == null ? "" : text.toString());
    }

Et la classe RenderText en question:

package play.mvc.results;

import play.exceptions.UnexpectedException;
import play.mvc.Http;
import play.mvc.Http.Request;
import play.mvc.Http.Response;

/**
 * 200 OK with a text/plain
 */
public class RenderText extends Result {

    String text;

    public RenderText(CharSequence text) {
        this.text = text.toString();
    }

    public void apply(Request request, Response response) {
        try {
            setContentTypeIfNotSet(response, "text/plain; charset=" + Http.Response.current().encoding);
            response.out.write(text.getBytes(getEncoding()));
        } catch(Exception e) {
            throw new UnexpectedException(e);
        }
    }

}

La suite va se passer dans ActionInvoker.java qui gère le cycle de vie de requête:

try {
	inferResult(invokeControllerMethod(actionMethod));
} catch (InvocationTargetException ex) {
	// It's a Result ? (expected)
	if (ex.getTargetException() instanceof Result) {
		actionResult = (Result) ex.getTargetException();

et plus tard:

result.apply(request, response);

Si vous utilisiez les Servlet en J2EE, vous aviez plutôt l’habitude de voir du code du genre:

package hall;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;

public class ThreeParams extends HttpServlet {
  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
      throws ServletException, IOException {
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    String title = "Reading Three Request Parameters";
    out.println(ServletUtilities.headWithTitle(title) +
                "<BODY>\n" +
                "<H1 ALIGN=CENTER>" + title + "</H1>\n" +
                "<UL>\n" +
                "  <LI>param1: "
                + request.getParameter("param1") + "\n" +
                "  <LI>param2: "
                + request.getParameter("param2") + "\n" +
                "  <LI>param3: "
                + request.getParameter("param3") + "\n" +
                "</UL>\n" +
                "</BODY></HTML>");
  }

  public void doPost(HttpServletRequest request,
                     HttpServletResponse response)
      throws ServletException, IOException {
    doGet(request, response);
  }
}

Je pense qu’il n’y a pas besoin de plus d’explications pour vous démontrer que Play vous rend la vie plus facile :).

Une réflexion au sujet de « Play – Controller et les variantes de render() (JSON, XML, fichier, …) »

  1. Ping : Play – Exécuter un template depuis un Controller (render, renderTemplate, …) | HTML5 Tutorial

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *