Étiquette : java

  • Gestion de session avec un cluster redis

    Voyons comment mettre en pratique les deux articles précédents (ici et ici).

    Les choses n’étant pas encore très évolués du cote de spring-data-redis pour le mode cluster, je suis revenu sur la version de session-webapp utilisant une valve tomcat.

    Les seules modifications apportées sont la suppression de l’image redis (je vais utiliser la version cluster) et l’utilisation d’une nouvelle image docker de base pour les serveurs Tomcat.

    En effet, j’ai modifié la valve pour utiliser la nouvelle classe JedisCluster à la place de Jedis.
    Mais pour que cela fonctionne, j’ai du utilisé la version 3.0.0-SNAPSHOT de jedis. La version releasée actuelle n’implémente pas les commandes binary par la lecture/ecriture.

    J’ai du également modifié l’image docker pour le cluster redis car par default, le cluster est configuré en 127.0.0.1. Mais quand le client java essaye de découvrir la topologie du cluster, il obtient ces ips, et ne peux pas se connecter aux autres noeuds. La modification permet d’établir la connexion sur l’ip du conteneur.

    Il ne reste plus qu’à lancer tout ce petit monde:

    redis:

    /docker-redis-cluster$ make-clean && make-build && make-run

    tomcat & haproxy

    /session-webapp$ ./stop_all.sh ;  mvn clean install && ./start_all.sh

     

    Lorsque je crée une session, je la vois bien sur un noeud redis et sur un autre noeud, je vois bien le message de redirection:

    http://172.17.0.29:8080/session-webapp/login.html?userName=toto

     

    docker-redis-cluster$ redis-cli -p 7000
    127.0.0.1:7000> get 9C58CB03A3E0898CEA70420FA681B626
    "\xac\xed\x00\x05w\b\x00\x00\x01M\xcf\x82%\xc6sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01M\xcf\x82%\xc6sq\x00~\x00\x00\x00\x00\x01M\xcf\x84\xd43sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexq\x00~\x00\x01\x00\x02\xbf sr\x00\x11java.lang.Boolean\xcd r\x80\xd5\x9c\xfa\xee\x02\x00\x01Z\x00\x05valuexp\x00sq\x00~\x00\x06\x01sq\x00~\x00\x00\x00\x00\x01M\xcf\x84\xd43t\x00 9C58CB03A3E0898CEA70420FA681B626sq\x00~\x00\x04\x00\x00\x00\x02t\x00\buserNamet\x00\x06benoitt\x00\aproductsr\x00$com.axioconsulting.test.bean.ProduitW\x02ts6\x14\xa0\xee\x02\x00\x02J\x00\x05countL\x00\x02idt\x00\x12Ljava/lang/String;xp\x00\x00\x00\x00\x00\x00\x00\x0et\x00\x05pates"
    127.0.0.1:7000> 
    docker-redis-cluster$ redis-cli -p 7001
    127.0.0.1:7001> get 9C58CB03A3E0898CEA

     

     

    Les sources sont ici:
    tomcat-session-manager
    webapp
    docker-redis-cluster

     

  • Redis Cluster en java

    En commençant à regarder le mode cluster de redis à travers une image docker déjà construire https://github.com/Grokzen/docker-redis-cluster, je me suis aperçu que ce n’est pas si transparent. En effaçant, si on essaye d’ajouter une valeur mais que la clé ne correspond au bon noeud, redis ne renvoi pas OK mais plutôt l’indication de ou insérer la donnée:

    $ redis-cli -p 7000
    127.0.0.1:7000> set toto foo
    (error) MOVED 9738 127.0.0.1:7001

    Je me suis demandé comment gérer cela dans un programme Java. Est-ce le driver qui va faire le travail ou il faut le gérer à la main.

    Si on utilise le driver standard de Jedis, voici ce qu’on obtiens:

    Jedis jedis=new Jedis("localhost",7000);
    jedis.connect();
    jedis.set("toto","bar");
    String value=jedis.get("toto");
    System.out.println("res - "+value);
    Exception in thread "main" redis.clients.jedis.exceptions.JedisMovedDataException: MOVED 9738 127.0.0.1:7001
    at redis.clients.jedis.Protocol.processError(Protocol.java:108)
    at redis.clients.jedis.Protocol.process(Protocol.java:142)
    at redis.clients.jedis.Protocol.read(Protocol.java:196)
    at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:288)
    at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:187)
    at redis.clients.jedis.Jedis.set(Jedis.java:66)
    at com.axioconsulting.redis.TestCluster.main(TestCluster.java:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

    Heureusement, Jedis propose une classe pour résoudre ce problème (Je n’ai rien trouvé coté JRedis).

    Set jedisClusterNodes=new HashSet(); jedisClusterNodes.add(new HostAndPort("localhost",7000)); 
    jc.set("toto","bar");
    String value=jc.get("toto");
    System.out.println("res - "+value);
    res - bar

     

    Le driver est même capable de récupérer les autres noeuds. Dans l’exemple j’ai indiqué un seul noeud, mais avec le code suivant :

    for (Map.Entry<string,jedispool> entry:jc.getClusterNodes().entrySet()){
       System.out.println(entry.getKey());
    }

    on voit bien que le driver a pu récupérer les autres noeuds:

    localhost:7000
    127.0.0.1:7004
    127.0.0.1:7005
    127.0.0.1:7002
    127.0.0.1:7003
    127.0.0.1:7000
    127.0.0.1:7001
    
    

    les sources sont ici

    Quelques liens utiles:
    – commandes redis cluster

    – À la découverte de Redis Cluster 3.0

     

  • Configurer Selenium par annotations

    Pour faire suite à l’article précédent sur la configuration spring, voici le même type de configuration pour Selenium (outil de test d’intégration pilotant un navigateur).

    On va pouvoir lancer les tests avec seulement 3 classes et 0 xml.

    1 – on définit une classe de configuration qui va contenir l’url de base du site et le driver du navigateur à tester:

    @Configuration
    public class TestConfig {
    
    @Bean
    public URI getSiteBase() throws URISyntaxException {
    return new URI("http://localhost:10001/demo-spring-1.0-SNAPSHOT");
    }
    
    @Bean(destroyMethod = "quit")
    public FirefoxDriver getDrv() {
    return new FirefoxDriver();
    }
    }

    2 – on définit ensuite une classe abstraite qui va référencer la configuration et va contenir un petit « tips » pour ne pas avoir plusieurs processus de firefox en même temps, lorsque qu’on lance plusieurs tests

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes={ TestConfig.class })
    public abstract class AbstractTestIT {
    
    @Autowired
    protected URI siteBase;
    
    @Autowired
    protected WebDriver drv;
    
    {
    Runtime.getRuntime().addShutdownHook(new Thread() {
    @Override
    public void run() {
    drv.close();
    }
    });
    }
    
    }

    3- et enfin la classe de test qui vérifie que le code du controller est correcte:

    public class HelloControllerTestIT extends AbstractTestIT {
    
    @Test
    public void testHello() {
    drv.get(siteBase.toString()+"/hello");
    assertTrue(drv.getPageSource().contains("Welcome anonymous"));
    }
    
    }

    4- pour que tout s’enchaîne correctement, il faut modifier le pom.xml

    <build>
    <plugins>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.12</version>
    <executions>
    <execution>
    <id>default</id>
    <goals>
    <goal>integration-test</goal>
    <goal>verify</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    <plugin>
    <groupId>org.codehaus.cargo</groupId>
    <artifactId>cargo-maven2-plugin</artifactId>
    <version>1.2.0</version>
    <configuration>
    <type>standalone</type>
    <configuration>
    <properties>
    <cargo.servlet.port>10001</cargo.servlet.port>
    </properties>
    </configuration>
    </configuration>
    <executions>
    <execution>
    <id>start</id>
    <phase>pre-integration-test</phase>
    <goals>
    <goal>start</goal>
    </goals>
    </execution>
    <execution>
    <id>stop</id>
    <phase>post-integration-test</phase>
    <goals>
    <goal>stop</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    </plugins>
    </build>

    et ajouter les dépendances suivantes:

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
    </dependency>
    <dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-firefox-driver</artifactId>
    <version>2.43.1</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
    </dependency>

    Attention: La version de selenium est fortement lié à la version de firefox. Si l’exemple ne marche pas, il faut vérifier qu’une nouvelle version n’est pas disponible.

    Le lancement de la commande

    mvn verify

    devrait donner un résultat du type:

    Results :
    
    Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

     

    Les sources sont ici: github

    références:

  • Configurer spring sans fichier XML

    Pour changer un peu de docker, voici un petit article pour initier un projet spring sans fichier de configuration XML.

    Je vais créer une petite application avec un Controller Spring MVC.

    Tout d’abord, on ajoute les dépendances. Servlet 3.1, spring-context , spring mvc suffisent:

    <properties>
    <spring.version>4.1.0.RELEASE</spring.version>
    </properties>
    
    <dependencies>
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
    </dependency>
    </dependencies>

    On va ensuite créer une classe qui va permettre de gérer l’initialisation de la configuration spring. Pour cela, j’utilise une nouveauté de servlet 3.0 qui est  l’interface WebApplicationInitializer:

    public interface WebApplicationInitializer {
    void onStartup(javax.servlet.ServletContext servletContext) throws javax.servlet.ServletException;
    }

    En implémentant une classe avec la méthode onStartup, on peut faire l’initialisation de son application. C’est plus simple que de faire une « fausse » servlet avec un paramètre loadOnStartup dans le fichier web.xml

    Voici le code de la classe que j’utilise:

    public class MyWebAppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
    WebApplicationContext context = getContext();
    
    servletContext.addListener(new ContextLoaderListener(context));
    ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(context));
    dispatcher.setLoadOnStartup(1);
    dispatcher.addMapping("/*");
    }
    
    private AnnotationConfigWebApplicationContext getContext() {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    context.setConfigLocation("com.axioconsulting.demo.spring.config");
    return context;
    }
    }

    La méthode getContext va permettre de gérer la configuration par annotation en commençant par le package com.axioconsulting.demo.spring.config.

    Dans ce package, j’ai défini 2 classes.

    @Configuration
    @ComponentScan("com.axioconsulting.demo.spring.controller")
    public class AppConfig {
    }

    L’annotation @Configuration permet à Spring de savoir que cette classe est à prendre en compte pour la configuration. Dans notre cas, on précise juste qu’il faut scanner le package com.axioconsulting.demo.spring.controller (c’est là que va se trouver le controller)

    @Configuration
    @EnableWebMvc
    public class WebMvcConfig {
    }

    Cette classe permet d’importer la configuration spring MVC.

    NB: On aurait pu fusionner les 2 classes.

    Il ne reste plus qu’à mettre en place le controller.

    @Controller
    public class HelloController {
    @RequestMapping("/hello")
    @ResponseBody
    public String hello(@RequestParam(required = false) String name){
    if (StringUtils.isEmpty(name)){
    return "Welcome anonymous";
    }
    return "Hello "+name;
    }
    }

    Il s’agit d’un controller basic qui va répondre un message sur /hello ou sur /hello?name=monNom

    anonyme monNom

    Et voila, on a réussi à initier une appli spring MVC en manipulant seulement des annotations ce qui me parait plus simple.

    A voir sur le long terme et sur un projet un peu plus gros si les annotations sont suffisantes.

    Les sources sont ici:  https://github.com/BenoitCharret/demoSpring