Comment implémenter des web services RESTful sans l’encombrant framework Spring ni la librairie Jersey?

Introduction

Dans le premier article autour du remplacement de Spring, j’ai décidé d’utiliser les composants fournis par Apache pour traiter l’intégralité du back-end (sauf peut-être la base de données, à voir). Dans cette série d’articles, je vais traiter du lien entre front-end et back-end via les web services REST.

Pourquoi REST et pas SOAP?

Dans la mesure où Apache CXF propose les deux types de web service, c’est une question qu’on peut se poser. La raison est plutôt simple: SOAP repose sur XML tandis que REST utilise JSON. Et manipuler des données JSON en Javascript, c’est extrêmement simple (alors que du XML c’est tout de suite plus compliqué).

Implémentation d’un service REST

L’implémentation se fait en deux étapes, d’un côté la configuration générale et de l’autre le service REST proprement dit.

Configuration

Il faut d’abord ajouter le fichier web.xml. Bonne nouvelle ici, on n’a rien de spécifique à ajouter. Le fichier est nécessaire parce le service REST fait partie d’une webapp.

Ensuite il faut ajouter une classe définissant l’application par son point d’entrée et les services accessibles.

web.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <icon>
        <large-icon></large-icon>
    </icon>
    <display-name>TEST</display-name>
    <description>This application allow users to test RESTful Web Service</description>

    <context-param>
        <param-name>webmaster</param-name>
        <param-value>web@gobothegeek.ch</param-value>
        <description></description>
    </context-param>

    <session-config>
        <session-timeout>30</session-timeout>    <!-- 30 minutes -->
    </session-config>
    
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>

Application

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package ch.gobothegeek.test.web;

import ch.gobothegeek.test.web.crud.WsTest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/rest")
public class TestApplication extends Application {
    private final Logger logger = LogManager.getLogger(TestApplication.class);

    public TestApplication() {
        logger.info("TestApplication is now running");
    }

    @Override
    public Set<Class<?>> getClasses() {
        return new HashSet<>(Arrays.asList(
            WsTest.class
        ) );
    }

    @Override
    public Set<Object> getSingletons() {
        Set<Object> singletons = new HashSet<>();
        return singletons;
    }
}

L’annotation @ApplicationPath est importante car c’est elle qui définit l’url de base des services. Ici tous les services auront une url de la forme https://<serveur>/<webapp>/rest.

Le service REST

Pour le service, on va avoir besoin de deux ou trois classes: le service proprement dit, une classe définissant les informations envoyées par le client et une classe définissant les informations à renvoyer au client (on peut utiliser la même classe pour l’envoi et la réception).

Le service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package ch.gobothegeek.test.web.rest;

import ch.gobothegeek.test.web.rest.params.WsTestParameter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.enterprise.context.RequestScoped;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@RequestScoped
@Path("/test")
public class WsTest {
    private final Logger logger = LogManager.getLogger(WsTest.class);

    public WsTest() {
        logger.info("WsTest constructor");
    }

    @Path("/lower")
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response doLogin(WsTestParameter paramTest) {
        paramTest.setMessage(paramTest.getMessage().toLowerCase());
        return Response.status(Response.Status.OK).entity(paramTest).build();
    }
}

L’annotation @Path sur la classe (optionnelle mais je recommande de la mettre afin d’organiser facilement les services et éviter les doublons d’url) définit l’url du groupe de services exposés dans la classe. Ici, les services proposés par la classe auront une url de la forme https://<serveur>/<webapp>/rest/test.

L’annotation @Path sur la méthode définit l’url du service (c’est optionnel mais cela permet de dissocier le nom de la méthode de son url). Ici le service est appelable par l’url https://<serveur>/<webapp>/rest/test/lower.

Les paramètres

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package ch.gobothegeek.test.web.rest.params;

public class WsTestParameter {
    private String message;

    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }

    @Override
    public String toString() {
        return "WsTestParameter{message='" + message + "'}";
    }
}

Dans cette classe on définit l’ensemble des informations manipulables par le client (autant pour l’envoi que la réception). Ici, côté client, on enverra un JSON de la forme {“message”:“TEST DE MESSAGE”} et on recevra en réponse {“message”:“test de message”}.

Conclusion

Apache CXF permet de développer rapidement des services REST, en très peu de configuration et de code.