insideIT.fr : le blog des architectes IT de SFEIR

Aller au contenu | Aller au menu | Aller à la recherche

lundi 30 janvier 2012

Noël dans les nuages !

Ou comment Google App Engine, permet de mettre en place un service supportant une très grosse charge dans un temps très court.



Le projet

L'Église Catholique propose depuis plus de 10 ans un site internet pour chercher les horaires de messes dans toute la France, qui souffrait historiquement d'un problème majeur : le trafic quotidien est raisonnable (environ 1 000 visites par jours, et des pointes à 5 000 visites le WE) mais le site doit faire face à d’importants pics de trafic à Noël et durant la semaine de Pâques.

Interface d'EgliseInfo

Or les années précédentes, l'ancien site (messesinfo.catholique.fr), développé en PHP/MySQL, ne tenait pas la charge lors de ces pics, s'écroulant sous les demandes des visiteurs. La technologie utilisée était tout à fait suffisante pour les besoins courants du site, cependant pour répondre à la forte charge sans changer de projet, il aurait fallu, soit disposer d’un serveur plus puissant, soit mettre en place du load balancing entre plusieurs serveurs. Dans les deux cas, ce n'aurait pas été très flexible, obligeant à s’équiper d’un serveur sur-dimensionné, pour seulement 6 jours dans l'année !



Nouveau projet

Nous avons donc fait le choix de migrer vers une technologie permettant de gérer cette surcharge ponctuelle, en ne payant que ce qui est réellement consommé. La refonte du site, permet également d’outrepasser les limitations imposées par l'ancien système (messes en semaine, autres types de célébrations) et d'ajouter de nouvelles fonctionnalités (événements, apis, widget, …).

Notre choix c'est porté sur Google App Engine, par la simplicité de développement que celui-ci apporte, le dimensionnement automatique des serveurs, les quotas gratuits et le paiement à la consommation.

Le projet a démarré en octobre 2010, avec la création d'une simple interface de recherche développé en GWT sur Google App Engine, mais laquelle se connectait ensuite à l'ancien site pour récupérer les résultats de la recherche. Cette première version à été mise en ligne pour Noël 2010, mais les deux sites n'ont pas tenu le coup car la base de donnée n'a pas accepté le nombre très élevé de requêtes simultanées ! Nous ne pouvions pas mettre les données de la base de donnée MySQL directement sur Google App Engine, car déjà l'interface d'administration n'est pas encore refaite, et la base de donnée chez App Engine, ne fonctionne pas exactement de la même manière.

Pour la période de Pâques 2011, Google venait de sortir en test la base de donnée Google Cloud SQL qui permet d'avoir des bases de données MySQL directement sur Google App Engine. Nous avons ainsi développé un service qui recopie la base de l'ancien site, sur le nouveau. Le système a fonctionné, mais du fait de la nouveauté du service et du manque d'optimisation de notre part, les bases de données SQL ont régulièrement planté.



Noël 2011

Visiteurs le jour de Noël 2011

EgliseInfo a relativement bien fonctionné pour Noël 2011, faisant face à une pointe de 1104 visiteurs simultané, et plus de 800 visiteurs en permanence durant toute l'après-midi, soit 100 000 visites en 3 jours.

Nous avons toutefois rencontré trois problèmes :

  • Vendredi soir vers 21h, nous avons dépassé le quota que nous avions fixé (25$), donc tous le site s’est retrouvé bloqué, le temps de changer et de mettre à 100$
  • Samedi matin, nous avons été limité par Google Cloud SQL a 100 requêtes simultanées. J'ai corrigé en faisait un balancing sur 2 bases SQL, et j'ai écrit un mail à Google : réponse dans la fin de l'après midi, ils ne peuvent pas modifier cette limite, et proposaient de passer à un serveur de plus grande capacité sans être vraiment sûr que cela résoudrait le problème : base de donnée plus grande, mais pas plus de connexion simultanées (cela changera plus tard), avec par ailleurs une interruption le temps de redémarrer le serveur. La double base de données a permis de réduire le nombre d'erreurs et de fournir une meilleure réactivité. Les responsables de Google SQL sont en train de tester avec notre jeu de données sur les requêtes les plus lentes.
  • Samedi 18h : EgliseInfo marchait toujours, mais les pubs ne s'affichait plus (un script PHP sur un serveur de la CEF) car le trop grand nombre de personnes sur tous les sites (le site de pub, messesinfo, les sites de paroisses et diocèses) à surchargé le firewall du datacenter où sont hébergés tout ces sites !

Analyse EgliseInfo

Record de visite : 1104MessesInfo (Ancienne version du site fait en PHP, qui est encore référencé) redirigeait presque tous le trafic vers EgliseInfo, redirection javascript depuis la page de résultat MessesInfo (pouvait venir d'une recherche Google) vers la recherche correspondante sur EgliseInfo. Donc pas de plantage du côté de MessesInfo, et une forte charge sur EgliseInfo qui a bien fonctionné avec des temps de réponse acceptables (forcément plus lent qu'habituellement).

Evolution des instances App EngineGoogle nous a mis en place automatiquement jusqu'à 400 serveurs disponibles (instances de l'application, qui sont virtualisées sur plusieurs serveurs réels).



Conclusion

En conclusion, Google App Engine a vraiment permis de gérer la très forte charge du site pour un coût tout à fait raisonnable. Pour les prochaines fois, il nous faudra mettre en place une répartition sur plusieurs bases de données, et dégrader les fonctionnalités pour accélérer les recherches.

vendredi 25 mars 2011

Retour d'expérience d'un projet GWT : 8 Internationalisation (8/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Dans le cadre du projet, nous avons mis en place l'internationalisation de l'application. Voici la documentation officielle de Google :

Nous avons ajouté le module I18N dans le module GWT "Module.gwt.xml" :

<inherits name="com.google.gwt.i18n.I18N"/>


Internationalisation en JAVA

Nous avons créé 3 classes :

  • /src/main/java/bws/i18n/ModuleConstants.java de type Constants
  • /src/main/java/bws/i18n/ModuleConstantsWithLookup.java de type ConstantsWithLookup
  • /src/main/java/bws/i18n/ModuleMessages.java de type Messages

Ainsi que les fichiers properties suivant qui correspondent à la traduction par défaut :

  • /src/main/resources/bws/i18n/ModuleConstants.properties
  • /src/main/resources/bws/i18n/ModuleConstantsWithLookup.properties
  • /src/main/resources/bws/i18n/ModuleMessages.properties

Et pour la traduction en français :

  • /src/main/resources/bws/i18n/ModuleConstants_fr.properties
  • /src/main/resources/bws/i18n/ModuleConstantsWithLookup_fr.properties
  • /src/main/resources/bws/i18n/ModuleMessages_fr.properties

Les fichiers ".properties" doivent être au format UTF-8, et les apostrophe (') doivent être dédoublé pour les interfaces étendant l'interface Messages (pas pour les Constants)

Par exemple pour afficher un message internationalisé depuis du code java d'une classe cliente (GWT) vous devez respecter la procédure suivante :

1/ Créer une méthode dans ModuleConstants.java, par exemple :

String warnCloseWindow();

2/ Ajouter une entrée dans les fichiers ModuleConstants*.properties avec la traduction correspondante:

warnCloseWindow=Are you sure you want to close this window?

3/ Utiliser cette clé dans votre classe java :

Window.alert(ModuleConstants.CONSTANTS.warnCloseWindow());

Pour la classe ModuleConstantsWithLookup, cela fonctionne de la même façon (méthode + clé correspondante) mais il est également possible de récupérer la valeur d'une clé à partir de la clé et sans appelé la méthode directement. Comme dans le code suivant ou on cherche à récupérer la valeur pour la clé "theKey":

ModuleConstantsWithLookup.CONSTANTS.getString("theKey");

Pour la classe ModuleMessages, il est possible de donner des paramètres aux méthodes qui seront intégrer dans la traduction. Un texte ne se place pas forcement au même endroit en fonction de la traduction (au début ou à la fin).

On met le numéro du paramètre (en commençant à 0) entre accolades (ex {0}), il est ensuite possible d'appliquer un formatage ({1,number} ou {0,date,medium} : en utilisant le formatage de la langue courante). On peux aussi gérer les formes singuliers et pluriels :

String cartItems(@PluralCount int itemCount);

et dans le fichier ".properties" :

cartItems = There are {0,number} items in your cart.
cartItems[one] = There is 1 item in your cart.
cartItems[none] = There is no item in your cart.


Internationalisation avec UiBinder

Pour traduire les libellés contenus dans des écrans UiBinder*.ui.xml vous devez encadrer la chaîne de caractère avec des balises spécifiques. Il vous faut tout d'abord ajouter ceci à votre fichier *.ui.xml dans la balise ui:UiBinder: ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat' ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator" ui:generateLocales="default"

Pour avoir au final:

<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"    
 ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
 ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator"   
 ui:generateLocales="default"
 xmlns:g="urn:import:com.google.gwt.user.client.ui">
...
</ui:UiBinder>

Par exemple, pour traduire : "where you can build your own workspace with widget" Il faut remplacer la chaine par :

<ui:msg description="FourthSentence">where you can build your own workspace with widgets.</ui:msg>

Et ce va automatiquement créer une nouvelle clé pour ce libellé. Vous trouverez les fichier properties générés dans target/extra/module

Ils sont nommés avec le full qualified name du fichier java concaténé avec le nom du fichier ui.xml puis suffixés avant l'extension par GenMessages... Simplissime non ? Merci Google (Cela évoluera très prochainement dans les futures release de GWT pour être simplifié)

Par exemple:
module.client.homepage.HomePageMenuHomePageMenuUiBinderImplGenMessages.properties

Ensuite nous positionnons un fichier dans le répertoire ou se trouve le fichier ui.xml à internationaliser, par exemple pour HomePageMenu.ui.xml :

/src/main/java/module/client/homepage/HomePageMenuHomePageMenuUiBinderImplGenMessages.properties

et évidemment :
/src/main/java/module/client/homepage/HomePageMenuHomePageMenuUiBinderImplGenMessages_fr.properties


Utilisation simplifiée

Une autre technique consiste à utiliser une petite ruse de Google : Si le compilateur, ne trouve pas le fichier de properties selon le nom donnée, il va chercher dans le fichier :
/src/main/ressources/com/google/gwt/i18n/client/LocalizableResource.properties

ou pour la version française :
/src/main/ressources/com/google/gwt/i18n/client/LocalizableResource_fr.properties

Il est ainsi possible de mettre toutes les traductions des fichiers UiBinder dans ce fichier et avoir un gestion simplifier de toutes les traductions. Pour créer ce fichier, un développeur a créé un petit script Python pour scanner tous les fichiers "*.properties" générées par le compilateur et les ajouter dans ce fichier. Il fait ensuite un mixte avec les clés de langues déjà existantes, et indique dans un commentaire, les textes à traduire, et surtout ceux dont la description a changé et qu'il faut peut être revoir la traduction.

Le script python mergelocales.py est disponible ici

Il suffit de le lancer avec :

mergelocale.py target/extra/module src/main/ressources/com/google/gwt/i18n/client/

mardi 22 mars 2011

Retour d'expérience d'un projet GWT : 7 Mise en place de Gin sur le projet (7/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Définitions de GIN/GUICE

Définition de Gin# : “GIN (GWT INjection) brings automatic dependency injection (DI) to Google Web Toolkit client-side code. GIN is built on top of Guice and uses (a subset of) Guice's binding language. (See GuiceCompatibility for details.) By using GWT's compile-time Generator support, GIN has little-to-no runtime overhead compared to manual DI.”

Définition de Guice# : “Guice is a dependency injection (DI) container”

Pour résumer, Gin est une librairie permettant de faire de l’injection de dépendance par paramètrage sur des projets GWT. Le but de l’injection de dépendance est de se passer du code permettant de relier les différents modules de l’application entre eux en le centralisant, ce qui améliore la lisibilité du code. Pour cela, il suffit de créer une factory ainsi que la configuration du binding des différents modules.

Configuration de Gin

Ajout de Gin en dépendance du projet (Maven / Classpath)

Il faut que les librairies Gin et Guice soient dans les dépendances Maven / Classpath du projet. Pour cela, il faut ajouter dans le pom.xml les dépendances suivantes :

     <dependency>
          <groupId>com.googlecode.gwt.inject</groupId>
          <artifactId>gin</artifactId>
          <version>1.0</version>
          <scope>provided</scope>
      </dependency>
      <dependency>
          <groupId>com.google.inject</groupId>
          <artifactId>guice</artifactId>
          <version>2.0</version>
          <scope>provided</scope>
      </dependency>

Le scope provided est utilisé pour que ces librairies ne soient pas embarquées dans le war généré par le projet qui les utilise.

Ajout de Gin en dépendance du projet (GWT)

Il faut que les librairies Gin et Guice soient dans les dépendances GWT du projet. Pour cela, il faut ajouter dans le Module.gwt.xml la dépendance suivante :

<module>
...
<inherits name="com.google.gwt.inject.Inject"/>
...
</module>

Création d’un injecteur de dépendances Gin

Pour pouvoir récupérer les modules dont les dépendances sont gérées par Gin, il suffit de créer une interface annotée dans laquelle on défini pour chacun des modules de l’application une méthode permettant de le récupérer.

@GinModules(ModuleInjectorModule.class)
public interface ModuleGInjector extends Ginjector {
  AppPresenter getAppPresenter();
  FooterPresenter getFooterPresenter();
  DisplayFactory getDisplayFactory();
  GlobalLayout getGlobalLayout();
}

Par exemple, dans notre cas, nous avons besoin de Gin pour gérer les dépendances du Presenter AppPresenter, nous avons donc défini une méthode getAppPresenter qui retourne un AppPresenter. En appelant cette méthode, Gin va s’occuper d’instancier automatiquement le Presenter en lui injectant les dépendances telles qu’elles sont configurées dans le module de configuration du binding Gin.

L’interface est annotée avec l’annotation @GinModules qui prend en paramètre la class du module de configuration du binding Gin.

Création du module configuration du binding Gin

Gin a besoin de connaitre la configuration du binding des modules de l’application. Pour cela, il faut créer une classe dans laquelle les liaisons entre les modules sont définies.

public class ModuleInjectorModule extends AbstractGinModule {
  @Override
  protected void configure() {
      //Say to GIN to bind the Display to the FooterView and
      //use a Singleton scope for the application
      //This singleton view will be injected to the MainLayout
      bind(FooterPresenter.Display.class).to(FooterView.class).in(Singleton.class);
      bind(GlobalLayout.class).in(Singleton.class);
      bind(AppPresenter.Display.class).to(GlobalLayout.class).in(Singleton.class);
  }

  /**
   * This Provider is used by GIN to inject all HandlerManager field
   * Use the legacy GlobalEventBus.get()
   */
  @Provides
  HandlerManager getGlobalEventBus() {
      return GlobalEventBus.get();
  }
}

Par exemple, dans notre cas, le FooterPresenter définie sa vue via l’inner classe Display qui est implémentée par la FooterView et cette classe n’a qu’une seule instance dans l’application. Ce qui se traduit par la ligne suivante :

bind(FooterPresenter.Display.class).to(FooterView.class)
                                  .in(Singleton.class);

Il est également possible de fournir a Gin des classes dont il ne gère pas le cycle de vie. Par exemple, dans notre cas, comme nous avons progressivement migré vers Gin, et pendant cette phase de migration, certaines classes devaient être a la fois utilisée par Gin et également être utilisée par le système d’injection de dépendance manuel utilisé précédemment.

C’est le cas pour notre Bus d'évènement par exemple, c’est pourquoi nous avons ajouté cette méthode :

  @Provides
  HandlerManager getGlobalEventBus() {
      return GlobalEventBus.get();
  }

Instancier le Gin Injector dans l’application et l’utiliser

Pour utiliser l’injecteur de dépendance Gin, il suffit d’ajouter dans le Module GWT de l’application un attribut static puis de l’utiliser comme suit:

public class Module implements EntryPoint {
   private final ModuleGInjector injector =  GWT.create(ModuleGInjector.class); 
   public void onModuleLoad() {
      ...
      MyPresenter myPresenter = injector.getMyPresenter();
      ...
   }
}

Il est nécessaire de faire ce GWT.create une fois dans l'application pour que GIN puisse être appeler au moins une fois.

Aller plus loin avec Gin

Gin permet de binder les classes GWT utilisant le deferred binding comme les interfaces des constantes et messages pour l'internationalisation, ce qui facilite leur utilisation car c’est Gin qui s’occupera de les instanciers pour nous.

Gin permet également de binder les services RPC GWT pour faciliter leur utilisation.

Gin permet d’utiliser des fonctionnalités de Guice dans GWT mais toutes ne sont pas accessibles à cause des limitations de GWT.

Voir le tableau récapitulatif des fonctionnailités disponibles

Voir la documentation de Guice pour plus d’informations

mardi 1 mars 2011

Retour d'expérience d'un projet GWT : 6 Ecrire des tests unitaires avec Mockito (6/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Tester une application GWT avec une architecture MVP permet de pouvoir écrire des tests JUnit en java pure sans utiliser l'implémentation GWT de JUnit qui démarre un serveur web et un navigateur pour lancer les tests unitaires. Ce qui prend du temps si l'on doit exécuter ses tests à chaque modification de l'application.

Pour qu'il soit possible d'écrire des tests en Java pure, il faut que le Présenter ne possède aucune référence à des classes du package Ui de GWT. C'est à dire toutes les classes qui étendent Widget et qui permettent d'afficher des informations, ce qui logiquement doivent être définie dans la vue. Il faut ainsi écrire un contrat entre le présenteur et la vue sous la forme d'une interface Display. Ce contrat ne doit posséder que des paramètres et valeurs de retour définie par une interface ou pouvant être utiliser par Java (pas de TextBox getLogin() mais un HasValues<String> getLogin() ou toute autre classe faisant référence à des Widget GWT)

De même, il n'est pas possible d'utiliser GWT.create() autrement dit, pour instancier les services RPC, les classes de Constantes pour l'internationalisation, etc ...

Il est donc nécessaire de faire ses instanciations en dehors du Presenter, et ne prendre en paramètre que l'interface de ceux-ci. Il deviens ainsi possible de créer une version spécifique pour les tests, en règle générale, on va écrire des classes qui implémentent les interfaces avec du code minimal pour réaliser nos tests (getter, setter, hash maps pour enregistrer les données, calcule simple ...). Ces classes doivent être maintenue lors que l'on modifie l'application, et peuvent avoir des bugs qui empêche le test de fonctionner correctement.

C'est là que Mockito prend toute sa puissance, en implémentant automatiquement ses interfaces et en ajoutant le comportement de ceux-ci.

Il faut ajouter la dépendance à Mockito à Maven :

     <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.8.1</version>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>org.mockito</groupId>
         <artifactId>mockito-all</artifactId>
         <version>1.8.5</version>
         <scope>test</scope>
     </dependency>

Les tests doivent être écrit avec JUnit 4 et être exécutés par le Runner spécifique de Mockito (si on veux utiliser les annotations automatique permettant de créer les mock et les arguments captor)


Mockito

Mockito est un générateur automatique de doublures. Il fonctionne sur le mode de l'espion :

  • On crée les mocks;
  • On décrit leur comportement
  • Ensuite, à l'exécution, toutes les interactions avec les mocks sont mémorisés ;
  • à la fin du test, on interroge les mocks pour savoir comment ils ont étés utilisés.

Toutes les méthodes de mockito sont des méthodes static de la classe Mockito, il est donc nécessaire d'importer statiquement ses méthodes pour simplifier l'écriture :

import static org.mockito.Mockito.*;

On crée un mock soit avec la méthode mock :

MonInterface mock = mock(MonInterface.class);

soit en utilisant l’annotation @Mock:

@Mock
MonInterface mock;

Il n'est pas nécessaire que MonInterface soit une interface, il est possible de mocker une classe (mock(Widget.class) permet d'utiliser une classe GWT dans le test dans que cela pose de problème, une version vide ayant les mêmes méthodes publique est créé par mockito)

On décrit ensuite le comportement attendu de notre mock :

when(mock.maMethode()).thenReturn(42);

On appelle les tests, qui font des interactions avec notre mock, et enfin on vérifie ses interactions

verify(mock).maMethode();

Cette dernière ligne vérifie que la méthode maMethode() a bien été appelé durant le test.


Définir le comportement d'un mock

Pour définir, le comportement d'un mock, on utilise when() auquel on passe en paramètre l'appel que l'on veux définir, c'est à dire l'appel de la méthode avec les paramètres que l'on doit répondre :

interface Add {
    public int add(int a, int b);
}

Si on veux émuler le comportement de notre méthode add qui additionne les deux paramètres, on va donner les résultats pour des paramètres données :

Add mockAdd = mock(Add.class);
when(mockAdd.add(1, 1)).thenReturn(2);
when(mockAdd.add(5, 7)).thenReturn(12);

Ainsi si dans notre test, on appelle mock.add(1, 1), le retour sera 2.

On peux donner plusieurs valeurs de retour qui seront retourné dans l'ordre :

Par exemple, si on veux simulé les retours de la methode random, on fera :

when(randMock.random()).thenReturn(5, 18, 86, 42);

Ainsi le premier appel de random() retournera 5, et le quatrième appel retournera 42 (ainsi que tous les suivants).

On peux aussi simuler l'envoie d'une exception avec la method thenTrow() :

when(mock.div(anyIn(),0)).thenTrow(new Exception("Division par zéro));

Qui lancera alors une exception lors que le deuxième paramètre est un zéro.


Vérifier les appels aux mock

La méthode verify() qui prend en parametre permet de vérifier qu'une méthode à bien été appeler :

verify(mock).mustCall(1)

Cette ligne permet de vérifier que la méthode mustCall a bien été appelé lors du test, et de plus permet de vérifier que le paramètre est bien 1.

On peux tester le nombre de fois où la methode a été appellé :

verify(mock, times(5)).add(anyString());

On vérifie ici que la méthode add a été appellé 5 fois avec une chaînes de caractères en paramètre.

Si on veux tester plus de choses sur les arguments, il est possible de les capter (les enregistrer) pour ainsi faire des tests plus poussé dessus :

ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(userservice).login(captor.capture());
assertEquals("login", captor.getValue().getLogin());

On peux aussi vérifier qu'il n'y a pas eu d'interaction avec un mock

verifyZeroInteractions(mock);


Utilisation avec GWT

Si on veux tester qu'un événements a bien été envoyé par GWT, on utilise les mock et les ArgumentCaptor :

@Captor
ArgumentCaptor<ValueChangeEvent<String>> valueChangeEventCaptor;

@Mock
ValueChangeEventHandler<String> handler;

@Test
void testValueChangeEventHandler() {
    TestClassWithValueChangeHandlers test = new TestClassWithValueChangeHandlers();
    //On s'enregistre dans l'eventBus à l'évènement click
    test.addValueChangeHandler(handler);
    //On appelle la methode à tester qui envois notre évènement ValueChange();
    test.setValue("newValue");
    //On vérifie que celui-ci a bien été appellé
    verify(handler).onValueChange(valueChangeEventCaptor.capture());
    //On test que la nouvelle valeur est bien danc l'object event :
    assertEquals("newValue",             valueChangeEventCaptor.getValue().getValue());
}

Références Documentation de Mockito

jeudi 10 février 2011

Retour d'expérience d'un projet GWT : 5 Implémentation Modèle-Vue-Présenteur (5/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Voyons maintenant comment créer une architecture Model-View-Presenter.

Le présenteur doit étendre la classe Presenter qui fournit des fonctionnalités de base à notre application. Comme l'accès au global event bus et à la factory de vue.

public class TestPresenter extends Presenter {

    /**
     * Interface represents the test view
     */
    public interface Display extends DisplayView {
        public void setUserName(String username);

        public HasClickHandlers getUserNameClickHandler();
    }
   
    // fields
    /** View */
    Display display;
   
    // constructor
    public TestPresenter(Display display, HandlerManager globalEventBus, DisplayFactory factory) {
        super(globalEventBus, factory);
        this.display = display;
       
        bind();
    }
   
    // Bind all event
    private void bind(){
                getGlobalEventBus().addHandler(
            OnUserLoggedEvent.getType(),
            new  OnUserLoggedEvent.Handler() {
            @Override
            public void onUserLogged(OnUserLoggedEvent event) {
                display.setUserName(event.getUser()
                        .getLogin());
            }
        });
        display.getUserNameClickHandler().addClickHandler(
            new ClickHandler() {
                public void onClick(ClickEvent event) {
                    getGlobalEventBus().fireEvent(
                        new OnUserLogout());
            }
        }       
    }

    public void go(Placer panel) {
        panel.setView(display);
    }
}

Nous avons ici l'architecture de base d'un presenter :

  • Le constructeur prend en paramètre toutes les interfaces qu'il va utiliser :
    • sa vue interfacé par l'interface interne au presenter : Display
    • le global event bus (Bus d'évènement global à l'application pour la communication entre les presenter)
    • la factory de vue (utilisée pour instancier les vues des sous-presenters)
    • Éventuellement des services RPC
  • la méthode bind() qui s'occupe d'enregistrer le présenteur aux évènements globaux de l'application (ici le login de l'utilisateur) et aux évènements de la vue (ici le click sur le label affichant le login de l'utilisateur).
  • la méthode go(), qui permet au présenteur de s'afficher
    • la classe Placer est une abstraction d'un Panel GWT, pour être complètement indépendant de GWT, cette interface possède une simple méthode setView qui prend en paramètre un DisplayView. L'interface DisplayView qui doit être étendu par toutes les vues demande juste d'implémenter la méthode asWidget() qui permet de retourner la vue (this) en tant que Widget GWT.

Créons maintenant la vue, en utilisant UiBinder :

public class TestView extends Composite implements TestPresenter.Display {

    private static TestViewUiBinder uiBinder = GWT
            .create(TestViewUiBinder.class);

    interface TestViewUiBinder extends
        UiBinder<Widget, TestView> {}

    @UiField Label label;
   
    public TestView() {
        initWidget(uiBinder.createAndBindUi(this));
    }

    @Override
    public HasClickHandlers getUserNameClickHandler() {
        return label;
    }

    @Override
    public void setUserName(String username) {
        label.setText(username);
    }

    @Override
    public Widget asWidget() {
        return this;
    }
}

Avec le fichier TestView.ui.xml associé :

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
    xmlns:g="urn:import:com.google.gwt.user.client.ui">
    <g:Label ui:field="label" />
</ui:UiBinder>

Cette classe étend donc notre Display du présenteur et accède aux composant GWT.

On peux ainsi tester facilement le code de notre présenteur car il ne possède aucune adhérence à GWT.

Nous avons choisi dans un premier temps de se passer de ces framework et d’appliquer les pattern MVP en suivant uniquement les pratiques conseillées par Google. Maintenant que nous avons une première expérience avec le développement MVP, nous pouvons penser à utiliser un de ces framework pour bénéficier de leurs utilitaires comme la gestion de l’historique, du pattern action etc. Si nous devions en choisir un à ce jour, ce serait gwt-platform qui est issu de gwt-presenter et gwt-dispatch.

jeudi 4 novembre 2010

Nouveautés GWT 2.1

GWT 2.1 vient de sortir en version finale, voici les nouveautés apportées par cette version :

  • Data Presentation Widgets
  • MVP Framework (Activities and Places)
  • RequestFactory
  • Editor
  • Server-side Speed Traces
  • Logging
  • Safe HTML

Détaillons un peu ces nouvelles fonctionnalités :

Data Presentation Widgets

Ce nouveau framework permet de construire des tableaux dynamiques (liste, arbres). Ces composants peuvent afficher un très grand nombre de données, car il va construire les données en minimisant les appels au DOM de la page.

MVP Framework (Activities and Places)

Depuis 2 ans, Google pousse le design patern Model View Presenter (MVP) grâce à une présentation faite par Ray Ryan à Google IO ainsi que deux articles :

http://code.google.com/intl/fr/webtoolkit/articles/mvp-architecture.html

http://code.google.com/intl/fr/webtoolkit/articles/mvp-architecture-2.html

De nombreux frameworks ont permit d'implémenter ce nouveau design pattern :

A la différence des frameworks précédents, la version de GWT 2.1 utilise une autre terminologie, en voici un petit lexique, et explication.

Vue

Tous d'abord, la notion de vue n'existe plus, une vue est un simple widget GWT ou plutôt un composite qui peut être une classe UiBinder.

La nouveauté c'est que tous les widgets Gwt implémentent maintenant l'interface IsWidget, qui demande une seule méthode Widget asWidget();

Maintenant une vue dont l'implémentation est un simple Widget peut être définie par une interface, tout en pouvant ensuite l'ajouter dans un conteneur.

Une vue étend une interface du Presenter qui permet à celui-ci de communiquer avec la vue.

Activité (Presenter)

Un Presenter se nomme maintenant une activité (Activity), et s'occupe de toute la logique métier de l'écran.

Un Presenter peut implémenter une interface venant de sa vue pour simplifier la gestion des événements avec UiBinder, c'est à dire qu'on crée dans la vue la méthode onClick accompagné de l'annotation @UiHandler, dans laquelle on appelle une méthode du Presenter (Activity) qui gérera ensuite l'action. L’ancienne technique avait pour principe de définir une méthode dans la vue (getter) qui retourne une interface permettant d’ajouter un Handler par le Presenter (i.e. HasClickHandlers).

Une activité doit implémenter deux méthodes :

  • start(AcceptsOneWidget containerWidget, EventBus eventBus) : Appelé lorsque l'activité doit s'afficher
  • onStop() : Appelé lorsque l'activité doit disparaître (lors qu'une autre activité viendra la remplacer ou que l'on quite la page)

Deux autres méthode doivent aussi être implémentées :

  • mayStop() demande un message à afficher si on veux demander à l'utilisateur si il veux réellement changer de page (indiquer qu'ils n'a pas sauvegarder un formulaire par exemple). Il suffit de retourner null si l'activité accepte le changement de page.
  • onCancel() : Appelé si l'activité n'a pas encore eu le temps d'être afficher et qu'elle doit déjà être remplacer par une autre activité.

Remarque: En étendant la classe AbstractActivity, seule la méthode start() doit être implémentée.

AcceptsOneWidget est aussi une nouvelle interface ajoutée à Gwt laquelle est implémentée par SimplePanel, et dans laquelle la vue doit être ajoutée. Cela évite de devoir utiliser l'interface HasWidgets ainsi que le Presenter fasse un clear() avant de faire un add() pour remplacer le widget composant la vue principale.

ActivityManager

L'ActivityManager, c'est un peu le pendant de l’AppControler (l'autre partie est remplacée par le PlaceController), car il s'occupe de changer les activités affichées lorsqu'une nouvelle page doit être affichée. Cette classe est directement implémentée par GWT 2.1. Chaque zone pouvant afficher des activités aura son propre ActivityManager.

Places

C'est là, la grosse nouveauté de ce nouveau framework MVP, car on ne retrouve pas cette information dans les autres frameworks MVP. Pour ceux qui connaissent la programmation Android, une Place a le même rôle qu'un Intent. Une Place représente le lieu où l'on se trouve, s'est à dire la page (activité) qui est affichée. Changer de page ou d'activité revient à changer de Place, d'où la méthode goTo(Place newPlace) qui est dans le PlaceControler.

Le PlaceControler représente ainsi la deuxième partie de l'AppControler, présenté par les anciens articles. A la différence de l’ActivityManager, le PlaceController est unique pour l’application.

Il ne peut pas y avoir deux Places affichées simultanément. Une Place peut contenir des données, c'est une simple classe à créer qui étend Place où l'on met les propriétés que l'on veut.

Pour qu'une Place puissent être utilisée comme URL (et ainsi utiliser les fonctionnalités précédent et suivant du navigateur), il faut utiliser le PlaceHistoryMapper qui permet de transformer une Place en String et vice versa.

Gwt propose de générer automatiquement cette classe. Il nous faut alors créer un Tokenizer pour chaque place qui va sérialiser et désérialiser les attributs d'une Place de la façon que l'on veut. Il va ensuite afficher dans l'URL le nom de la Place (nom de la classe pouvant être redéfinis par l'annotation @Prefix("")) La classe PlaceHistoryHandler s'occupera ensuite de changer l'url et la gestion de l'historique automatiquement.

ActivityMapper

Cette classe permet de faire la conversion entre une Place et une Activity. Elle s'occupe d'instancier l'Activity pour une Place donnée. C'est à nous de la créer mais avec Gin, cela peut se faire facilement.

EventBus

C'est un bus d'événement, il permet de faire communiquer tous les composants d'une application avec les autres applications. L'ancien HandlerManager n'existe plus, l'interface EventBus est désormais utilisée. Une implémentation simple existe : SimpleEventBus. Attention, une activité doit ajouter ses évènements dans le "start" car une fois qu'elle est supprimée les événements sont retirés automatiquement.

Model

Le model est implémenté par la RequestFactory qui permet de faire transiter des entities JPA /JDO entre le server et le client. Il apporte automatiquement les méthodes nécessaires pour construire un CRUD (Créer, Lire, Mettre à jour, Supprimer). RequestFactory n'a pas pour vocation à remplacer RPC et peux être utilisé indépendemment. Seules les données modifiées de l’entité transiteront sur le réseau.

Editor

Apporte un système de binding entre les objets java et les représentations graphiques en particulier pour créer les formulaires de création et de modification. Plus d'informations Vous pouvez retrouver la documentation officielle avec un projet d'exemple :

http://code.google.com/intl/fr/webtoolkit/doc/trunk/DevGuideMvpActivitiesAndPlaces.html

et voici quatre autres articles qui expliquent le fonctionnement des Places et Activities :

Server-side Speed Traces

Les informations de performances venant du serveur (Google App Engine ou Spring TC Server) peuvent maintenant être affichées dans l'outil Speed Tracer.

Logging

Il existait déjà Gwt-Log qui a été maintenant intégré directement à GWT 2.1. On utilise l'Api standard Java pour logger (java.util.logging). Par-contre la configuration de logging se fait dans le fichier .gwt.xml

# In your .gwt.xml file
<inherits name="com.google.gwt.logging.Logging"/>
# To change the default logLevel
<set-property name="gwt.logging.logLevel" value="SEVERE"/>  
# To disable logging        
<set-property name="gwt.logging.enabled" value="FALSE"/>      
# To disable a default Handler      
<set-property name="gwt.logging.consoleHandler" value="DISABLED"/>  

# In your .java file
Logger logger = Logger.getLogger("NameOfYourLogger");
logger.log(Level.SEVERE, "this message should get logged");

Les logs peuvent être ensuite affichés de différentes façons,

  • SystemLogHandler - Ecrit dans la sortie du mode de développement de GWT
  • DevelopmentModeLogHandler - Utilise l'ancienne méthode GWT.log (les logs ne sont visibles qu'en mode développement)
  • ConsoleLogHandler - Ecrit dans la console Javascript (Firebug ou console Chrome)
  • FirebugLogHandler - Ecrit dans la console Firebug
  • PopupLogHandler - Affiche les logs dans une popup
  • SimpleRemoteLogHandler - Envois les logs par un RPC où l'on peut logger ensuite sur le serveur.

Safe HTML

Safe HTML permet de filtrer du texte pour protéger le HTML qui le contient et éviter les injections XSS. Il fonctionne comme un StringBuilder, mais peut échapper automatiquement les caractères spéciaux. Un système de template permet d’écrire du code HTML facilement en utilisant un mécanisme similaire aux Messages mais en protégeant le code HTML.

public interface MyTemplates extends SafeHtmlTemplates {
   @Template("<span class=\"{3}\">{0}: <a href=\"{1}\">{2}</a></span>")
   SafeHtml messageWithLink(SafeHtml message, String url, String linkText,
       String style);
 }

 private static final MyTemplates TEMPLATES =
     GWT.create(MyTemplates.class);

 public void useTemplate(...) {
   SafeHtml message;
   String url;
   String linkText;
   String style;
   // ...
   InlineHTML messageWithLinkInlineHTML = new InlineHTML(
       TEMPLATES.messageWithLink(message, url, linkText, style));
   // ...
 }

Voilà un rapide tour d'horizon des nouveautés de GWT 2.1, d'autres articles suivront pour expliquer plus précisément certains points. Ces nouveautés une fois maitrisées et utilisée sont vraiment puissante et apporte encore plus de simplicités à la fois pour le développeur mais aussi pour l'utilisateur.

lundi 25 octobre 2010

Retour d'expérience d'un projet GWT : 1 Nouveautés de GWT 2.0 (1/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

1 Nouveautés de GWT 2.0

Development Mode (anciennement OOPHM : "Out Of Process Hosted Mode")

GWT 2.0 apporte une nouvelle approche du développement des applications GWT en permettant de débugguer le code java et de voir le résultat directement dans les navigateurs sans passer par un navigateur spécifique, anciennement via le HostedMode qui utilisait son propre Navigateur différent selon le système d'exploitation. On assiste ainsi à un changement de terminologies puisqu'il ne faut plus parler de "hosted mode", qui portait souvent à confusion, mais de "development mode". Et pour la même raison on parle maintenant de "production mode" au lieu de "web mode", pour parler du script compilé.

On peut ainsi utiliser les outils du navigateur pour développer comme par exemple firebug sans devoir recompiler en permanence toute l'application. Pour utiliser ce mode, il suffira de copier/coller l'url qui est donnée par GWT lors du démarrage du server dans le navigateur, par exemple: "http://localhost:8080/Tutoriel.html?gwt.hosted=X.X.X.X:NNNN", où les X représentent l'adresse IP de votre ordinateur et les N le port utilisé par GWT.

Le development mode a été pour nous un facteur de gain de productivité important par rapport au hosted mode car grace à cette fonctionnalité il est possible de tester directement l’application dans les navigateurs cibles. On regrettera simplement que cette fonctionnalité ne soit pas disponible dans Chromium, le projet open-source de Google Chrome sur Linux. Layout

La notion de Layout permet d'avoir des composants génériques pour gérer la disposition des widgets. Ces layouts utilisent des panels déjà existant mais uniformisent leur utilisations. De plus, ces layouts se redimensionnent automatiquement en réorganisant plus précisément les widgets. Le rendu a été amélioré pour s'afficher plus rapidement et le redimensionnement peut être animé. A la différences des anciens panels de GWT qui utilisent massivement les tableaux pour afficher les widgets, les nouveaux layouts utilisent uniquement le CSS. Il existe par exemple le StackLayoutPanel, le TabLayoutPanel ou le DockLayoutPanel qui permettent d'avoir un contenu principal (Center) et d'ajouter des contenus au Nord, au Sud, à l'Ouest et à l'Est autour de celui-ci.

Nous avons utilisé ces Layout dans l’application très simplement et notre Web Designer a été particulièrement content de voir le rendu HTML/CSS généré s’améliorer en les utilisant, ce qui a faciliter son travail.

RPC (Remote Precedure Call)

Le système de service distant a été refondu pour gagner en performance et permettre l'ajout de nouvelles fonctionnalités. Pour le développeur, le principe de fonctionnement ne change pas. Il suffit d'ajouter dans le fichier Tutoriel.gwt.xml la ligne <inherits name='com.google.gwt.rpc.RPC' /> et de changer l'interface RemoteService par RpcService ainsi que la classe RemoteServiceServlet par RpcServlet. Cependant, cette fonctionnalité n'est pas, pour l'instant, compatible avec Google App Engine.

ClientBundle

On avait vu que GWT dans ses versions précédantes était capable de réunir plusieurs images en une seule, GWT 2.0 va encore plus loin car il est maintenant capable de réunir différents types de ressources (Images, Texte, Css, Données, etc...) en même temps. Toutes les ressources sont intégrées dans un même bundle encodé en base 64. Rappelons ainsi l'avantage de ce concept: un seul fichier à télécharger, donc, moins de requêtes HTTP et un temps de chargement de la page plus rapide.

Code splitting

Tout le code javascript est compilé en un seul fichier, téléchargé au chargement de la page. Dans le cas de très grosses applications, la taille de celui-ci peut considérablement devenir très importante et ralentir, de ce fait, le temps de chargement de l'application au démarrage. Pour pallier à ce problème, GWT 2.0 offre maintenant la possibilité de couper notre application en plusieurs fichiers qui seront téléchargés quand ceux-ci seront nécessaires. Par exemple, si une partie du code est utilisé uniquement lors du déclenchement d'un événement, son téléchargement n'est pas forcément essentiel lors du premier chargement de l'application. Par ailleurs l'utilisation est très simple:

GWT.runAsync(new RunAsyncCallback() {
    @Override
    public void onSuccess() {
        //Tout le code qui est ici, sera téléchargé plus tard.
    }

    @Override
    public void onFailure(Throwable reason) {
        //Si le téléchargement du nouveau code a échouée
    }
});

Mais attention, si le code, qui se trouve dans le onSuccess, est aussi utilisé par l'application dès son lancement, il sera téléchargé au début. C'est pourquoi GWT génère, tout de même, un rapport qui donne toutes les informations de compilation, informations que l'on peut analyser pour optimiser son application.

De plus, il est important de prévenir l'utilisateur du chargement par une barre de progression, ou par une icône de préchargement, car le changement n'est pas forcement immédiat et peut influer sur l'utilisabilité de l'application. Pour utiliser cette fonctionnalité, ajoutez "-compileReport" dans les paramètres avancés du compilateur GWT. Les rapports de compilation sont alors générés dans le répertoire extras/[moduleName]/soycReport/compileReport/.

Nous avons utilisé cette fonctionnalité pour charger à la demande les écrans principaux de l’application. Nous avons constater que la taille globale des fichiers javascript générés a sensiblement augmentée suite a la mise en place du Code Splitting. Il faut noter que l’utilisation de cette fonctionnalité sur 12 permutations a nécessité dans notre cas d’augmenter la partition /tmp utilisée pour compiler l’application (4Go ne suffisait pas).

jeudi 1 octobre 2009

GWT 2.0, App Engine, et WolfEngine

Depuis cet été, nous faisons régulièrement des builds de GWT, vous pouvez les retrouver sur le compte google code créé à cet effet :

http://code.google.com/p/sfeir/

Nous avons aussi écrit deux tutoriaux pour apprendre à utiliser les nouveautés de GWT :

Par contre, nous avions une erreur en utilisant Google App Engine :

@@failed com.google.apphosting.utils.jetty.DevAppEngineWebAppContext@3225c9{/,.} javax.xml.parsers.FactoryConfigurationError: Provider

org.apache.xerces.jaxp.SAXParserFactoryImpl not found@@

Pour résoudre ce problème, il faut ajouter une jar à son projet : xercesImpl.jar Nous avons donc testé WolfEngine avec GWT 2.0, celui-ci fonctionne parfaitement, malgré des Warnings à la compilation sur les classes et méthodes dépréciées qui risquent de disparaitre à la version finale de GWT. Il est bon de tester dès maintenant ses applications pour faire les corrections adéquates et passez ensuite facilement dans la version 2.0.

De plus, le mode debug directement dans le navigateur est vraiment très pratique, on peut ainsi tester dans les navigateurs et utiliser les outils comme firebug lors du développement. Pour utiliser ce mode, il faut ajouter la jar gwt-dev-oophm.jar avant la jar gwt-(window|mac|linux).jar. Ou encore utiliser la dernière version du plug-in google et définir ajouter GWT 2.0 en bibliothèque. Une case à cocher apparait alors en dessous de l'URL dans l'onglet GWT pour sélectionner ce mode.

oophm

Il suffit ensuite de copier coller l'URL donnée dans la barre d'adresse de votre navigateur préféré. L'installation des plug-ins dans les navigateurs est expliqué sur la page affiché. Pour chrome, il faut utiliser les dernières builds (chanel dev), firefox c'est le plus simple, une extension à télécharger et installer. Et pour IE, il faut installer une dll. Amusez-vous bien !