Accueil > Code > GWT et les communications avec le serveur

GWT et les communications avec le serveur

jeudi 10 janvier 2013, par Nicolas

Introduction

Parmi ses nombreuses fonctionnalités, GWT permet évidemment au client de communiquer avec le serveur.

À part le cas où le serveur est également écrit en Java, ce qui permettrait d’utiliser les mécanismes RPC spécifiques, il faut évidemment définir un protocole de communication, pour lequel JSON est un choix logique pour transférer des données du serveur au client.

Classes côté client

La réponse retournée par le serveur étant à la base une chaîne de caractères, il faut, sauf cas très simples, la convertir en classe, afin de se simplifier la vie.

En supposant que la réponse soit de la forme

{ status: 0 }

une définition de classe basique serait

package org.weeger;
import com.google.gwt.core.client.JavaScriptObject;
public class SimpleReply extends JavaScriptObject
{
  /** Constructeur requis par GWT. */
  protected SimpleReply() { }
  /**
   * Statut général du serveur.
   * @return
   * 0 pas d'erreur, autre erreur.
   */

  public final native int getStatus() /*-{ return this.status; }-*/;
  /**
   * Convertit une réponse en objet.
   * @param json
   * réponse serveur à convertir.
   * @return
   * objet converti.
   */

  public static native SimpleReply asSimpleReply(String json) /*-{
    return eval(json);
  }-*/
;
}

Puis lorsqu’on reçoit la réponse du serveur, dans un onResponseReceived ou autre,

final SimpleReply reply = SimpleReply.asSimpleReply(dataFromServer);
if (reply.getStatus() == 0) {
    ...
}

Il est bien sûr simple de surcharger SimpleReply avec d’autres classes, qui contiennent des champs supplémentaires, comme

package org.weeger;
public class CreateItemReply extends SimpleReply
{
  /** Constructeur requis par GWT. */
  protected CreateItemReply() { }
  /**
   * Retourne l'identifiant du nouvel élément.
   * @return identifiant.
   */

  public final native int getId() /*-{ return this.id; }-*/;
  /**
   * Convertit une réponse en objet.
   * @param json
   * réponse serveur à convertir.
   * @return
   * objet converti.
   */

  public static final native CreateItemReply asCreateItemReply(String json) /*-{
    return eval(json);
  }-*/
;
}

Il est également possible d’avoir d’autres objets, via JsArray, dans les réponses.

PHP et le JSON

Si le serveur est écrit en PHP, la fonction json_encode permet d’encoder comme il faut tous les éléments pour faire du JSON valide.

Si par exemple on souhaite retourne l’identifiant d’un nouvel élément créé :

/* créer l'élément, récupérer son identifiant dans $newId */
$reply = json_encode(array('status' => 0, 'id' => $newId));

et le client n’a plus qu’à utiliser CreateItemReply pour récupérer les codes.

Il y a cependant un point auquel il faut faire très attention : PHP ayant beau être faiblement typé, il y a une notion de type, même cachée, même avec des conversions implicites.

Et, pour communiquer via JSON, c’est un point à prendre en compte et à ne pas oublier.

Si côté client on définit dans une réponse une fonction

public final native boolean isValid() /*-{ return this.isValid; }-*/;


alors côté PHP il faut s’assurer, via conversion explicite si besoin, que la valeur mise dans isValid est true ou false.

Si la valeur est 0 ou 1 ou toute autre (qui en PHP se convertit très bien et implicitement en boolean), le JSON contiendra non pas un boolean mais un number, certains navigateurs comme Chrome refuseront la conversion et provoqueront une erreur.

Le cas typique, surtout lors d’une création d’élément, est l’identifiant récupéré via PDO::lastInsertId(), qui retourne une chaîne. Bien sûr elle sera convertie implicitement en nombre si besoin, mais pour la retourner au client juste après création il faut faire cette conversion explicitement.

C’est dans ce genre de cas que l’on apprécie de pouvoir voir les erreurs du navigateur.

Communication avec un autre serveur que celui d’origine

Il peut y avoir des cas où le client a besoin de faire des requêtes sur un domaine qui n’est pas celui d’origine de la page.

La documentation Google est assez claire sur comment faire, mais l’exemple donné n’est pas très générique.

Aussi voici une classe utilisant AsyncCallback afin de faire une telle requête.

package org.weeger;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.rpc.AsyncCallback;
public class RequestMaker {
  /**
   * URL de base à appeler.
   */

  private final static String baseURL = "http://example.com/";
  /**
   * Identifiant de requête, pour éviter les duplications.
   */

  private static int requestId = 0;
  /**
   * Appel du serveur.
   *
   * @param request URL à appeler, y compris le fichier, par rapport à baseURL.
   * @param handler fonction à informer de l'avancement.
   */

  public native static void makeRequest(final String request, final AsyncCallback<String> handler) /*-{
   var callback = "callback" + @org.weeger.RequestMaker::requestId;
   @org.weeger.RequestMaker::requestId++;
   // [1] Create a script element.
   var script = document.createElement("script");
   script.setAttribute("src", @org.weeger.RequestMaker::baseURL + request + '&callback=' + callback);
   script.setAttribute("type", "text/javascript");
   // [2] Define the callback function on the window object.
   window[callback] = function(jsonObj) {
     // [3]
     if (handler != null) {
       handler.@com.google.gwt.user.client.rpc.AsyncCallback::onSuccess(*)(jsonObj);
     }
     window[callback + "done"] = true;
   }
   // [4] JSON download has 1-second timeout.
   setTimeout(function() {
     if (!window[callback + "done"] && handler != null) {
       handler.@com.google.gwt.user.client.rpc.AsyncCallback::onFailure(*)(null);
     }
     // [5] Cleanup. Remove script and callback elements.
     document.body.removeChild(script);
     delete window[callback];
     delete window[callback + "done"];
   }, 1000);
   // [6] Attach the script element to the document body.
   document.body.appendChild(script);
   }-*/
;
}

Ce code est une simple extension du code fourni par Google, et j’ai même laissé les indications de commentaire.

Côté serveur, il suffit de modifier le code pour appeler la fonction spécifiée :

      $json = json_encode(array('status' => 0 /* plus d'éléments si besoin */);
      echo $_GET['callback'] . '(' . json_encode($json) . ')';

et le tour est joué.