Exercices - WS Rest - Les locations⚓︎
Récupération de l'application
Vous trouverez ici un projet Web présentant une application de location (type AirBnb) :
Pour le faire fonctionner, il faut avoir une JDK 11, un serveur WildFly 30.0 et importer ce projet dans votre IDE.
Il faut également avoir une base de données nommée location_db :
create database location_db;
Pour créer la datasource sur le serveur WildFly, vous pouvez :
- L'ajouter depuis la console d'administration du serveur, comme ce que nous avons toujours fait.
-
Ou plus simplement en ajoutant au bon endroit le code suivant, dans le fichier 📄
standalone.xml
, lorsque le serveur est éteint.Pour accéder à ce fichier, il suffit, dans VSCode (ou VSCodium), de faire un clic droit sur le serveur,
Server Actions...
>Edit Configuration File...
.Ce fichier contient une balise
<datasources>
qui contient elle même des parties Drivers (qui a été complétée lorsque nous avons ajouté le pilote mysql), et des parties<datasource>
(où vous retrouverez toutes les sources de données que vous avez crées).Il faut donc ajouter le code suivant avec les autres
<datasource>
, en adaptant bien sûr le port, le login et le mot de passe📄standalone.xml<datasource jndi-name="java:/LocationDB" pool-name="LocationDB"> <connection-url>jdbc:mysql://localhost:3306/location_db</connection-url> <driver-class>com.mysql.cj.jdbc.Driver</driver-class> <driver>mysql</driver> <security user-name="root" password="pwd"/><!--(1)!--> <validation> <valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker"/> <validate-on-match>true</validate-on-match> <exception-sorter class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter"/> </validation> </datasource>
-
Si vous n'avez pas de mot de passe pour vous connecter, il faut supprimer l'attribut
password
, c'est-à-dire indiquer<security user-name="root"/>
-
Il faut ensuite créer le livrable (WAR) avec maven :
mvn clean install
IHM
L'application est maintenant disponible ici : http://localhost:8080/location/.
Cette IHM sert uniquement à vérifier que les 4 locations sont bien présentes dans la base de données. Nous ne ferons ici aucune modification sur cette IHM.
Dans ce TD, nous allons uniquement ajouter une partie Web Services pour exposer à d'autres applications certaines fonctionnalités.
Objectif du TD
L'objectif de ce TD est de compléter le projet fourni en proposant des WS Rest exposant les méthodes suivantes :
- Récupération d'une location (à partir de son identifiant).
-
Récupération des toutes les locations présentes dans la BDD.
- Il doit être possible de trier les locations (sur leur ville uniquement) de manière croissante ou décroissante.
- Il doit être possible de filtrer les locations par ville.
-
Suppression d'une location (à partir de son identifiant).
- Suppression de toutes les locations (ce service ne sera pas implémenté).
- Création d'une location.
- Mise à jour totale d'une location (à partir de son identifiant).
- Mise à jour partielle d'une location (à partir de son identifiant).
Exercice 1 - Spécification de l'API⚓︎
Exercice 1 - Spécification de l'API
Réfléchissez à la structure de vos URLs, et au verbe HTTP utilisé pour implémenter cette liste de service.
⚠️ Il faut bien sûr respecter les différentes règles d'une architecture REST (les 3 premières en fait) (Cf. cours à partir de la page 26).
Exercice 2 - Affichage de location(s)⚓︎
Exercice 2 - Affichage de location(s)
Implémenter les 2 premiers services.
Dans les deux cas, le client doit pouvoir choisir de recevoir les données en XML ou en JSON. Si la réponse est demandée dans un autre format, une erreur 406
doit être retournée.
Pour tester les Web Services Rest, nous allons utiliser Postman (Cf. ici).
- On sélectionne le verbe HTTP souhaité.
- On indique l'URL permettant d'accéder au service souhaité.
- On sélectionne l'onglet Params,
-
et on indique les éventuels paramètres de requête (les
QueryParam
). -
On sélectionne l'onglet Headers, si on souhaite ajouter des informations dans l'en-tête de la requête HTTP,
- par exemple pour indiquer ce qu'on souhaite comme type de retour.
- On envoie la requête.
- On obtient le code HTTP retour,
- et en dessous, l'éventuel résultat de la requête.
Nous allons utilises pour JaxRS pour implémenter des Web Services Rest en JEE (JaxRS est une spécification, Jackson est l'implémentation de cette spécification).
Pour cela, il suffit de créer une classe qui implémente jakarta.ws.rs.core.Application
, et qui contient l'annotation @ApplicationPath
. Cette classe est créée par défaut avec l'archetype maven que nous utilisons, c'est la classe JakartaRestConfiguration
. Dans ce projet, il faut la créer. Nous allons l'appeler WebApp
, et nous la placerons dans le package fr.univtours.polytech.location.rest
:
package fr.univtours.polytech.location.rest;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
@ApplicationPath("api")
public class WebApp extends Application {
}
Il faut ensuite créer une seconde classe, qui sera le Web Service. Elle doit contenir une annotation @Path
. Chaque méthode qui correspond à un service doit également contenir cette annotation, ainsi que celle correspondant au verbe HTTP qui permet de l'appeler. Nous l'appellerons LocationRest
et nous la placerons dans ce même package fr.univtours.polytech.location.rest
.
Concernant les paramètres des méthodes, il faut utiliser les annotations @QueryParam
, @PathParam
, @HeaderParam
ou @FormParam
en fonction du cas (Cf. cours).
Il faut utiliser l'annotation @Produces
, et utiliser les MediaType
(en faisant attention au package) pour indiquer que XML et JSON peuvent être générés. On peut ici indiquer une liste de formats acceptés, entre {}
. Si un autre format est demandé, une erreur 406
est automatiquement renvoyée.
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@XMLRootElement
sur le bean correspondant.Pour que les annotations de JaxB (par exemple
@XmlTransient
) fonctionnement également lors du mapping en JSON, il faut ajouter la dépendance suivante dans le 📄pom.xml
:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.17.1</version>
</dependency>
Enfin, côté client, il faut ajouter la clef Accept
dans l'en-tête de la requête, avec la valeur application/json
ou application/xml
.
Exercice 3 - Suppression de location(s)⚓︎
Exercice 3 - Suppression de location(s)
Implémenter les services de suppression.
Dans cet exercice, on n'implémentera que le service de suppression d'une seule location (le numéro 3).
Pour ceux-ci, l'utilisateur doit être identifié. Nous n'allons pas géré ici toute la partie authentification, mais nous allons imaginer que l'authentification est gérée en amont, et que lorsque l'utilisateur est correctement identifié, un jeton d'authentification lui est renvoyé. Ce jeton est ensuite utilisé, et ajouté dans le header des requêtes HTTP qui le nécessitent. Ce jeton doit être associé au code Authorization
(la liste des en-tête est disponible ici).
sequenceDiagram
participant Client du WS
participant Serveur (le WS)
Client du WS ->> Serveur (le WS): Connexion
Serveur (le WS) -->> Client du WS: Token envoyé (ici 42)
Client du WS ->> Serveur (le WS): Requêtes Rest ...
Ici, on se contentera de vérifier que le jeton Authorization
présent dans l'en-tête de la requête HTTP est 42
. Si la clef Authorization
n'est pas présente dans l'en-tête, une erreur 401
doit être renvoyée. Si cette clef est bien présente, mais avec une valeur autre que 42
, une erreur 403
doit être renvoyée. La liste des codes HTTP est disponible ici.
Pour le service de suppression d'une location donné, si cette location n'existe pas, il faut renvoyer une erreur 404
.
Quel code faut-il renvoyer lorsque la location a bien été supprimée (et que tout s'est bien passé) ?
-
Gérer l'en-tête de la requête HTTP.
Il faut penser à utiliser l'annotation
@HeaderParam
sur les paramètres concernés. Il faut utiliser l'interfaceHttpHeaders
pour les noms de ces paramètres, par exemple :☕ Code Java@HeaderParam(HttpHeaders.AUTHORIZATION)
-
Indiquer un code retour HTTP.
Pour cela, il faut que la méthode qui implémente le service retourne un objet
Response
. Pour renvoyer un401
par exemple, il suffira d'écrire le code suivant :☕ Code Javareturn Response.status(Status.UNAUTHORIZED).build();
Pour indiquer un code
200
, on peut directement écrire :☕ Code Javareturn Response.ok().build();
Il est également possible d'ajouter la liste des beans dans la réponse de cette manière :
☕ Code Javareturn Response.ok(this.locationBusiness.getLocations()).build();
Exercice 4 - Création d'une location⚓︎
Exercice 4 - Création d'une location
Implémenter le service de création (5).
Il faut pouvoir l'appeler via un formulaire HTML, ou en envoyant directement toutes les informations dans le corps de la requête, au format XML ou JSON. Si un autre format est utilisé, une erreur 415
doit être renvoyée.
Quel code doit être renvoyé lorsque tout se passe bien ?
Bonus 1 : Dans la création depuis un formulaire, ajouter la possibilité d'indiquer l'URL d'une image.
Bonus 2 : Pour ce service également, l'utilisateur doit être identifié.
-
Créer une location
Il y a deux manières de créer une location :
- En envoyant directement toutes les informations dans le corps de la requête, en JSON ou en XML par exemple.
- En envoyant des informations en paramètres de la requête, exactement comme lorsqu'on soumet un formulaire HTML. On utilise alors des
FormParam
.
Il va donc y avoir deux méthodes pour créer une location, donc les signatures sont :
☕ Code Java// Méthode appelée lorsqu'on ajoute toutes les informations dans le corps de la requête. public Response createLocation(LocationBean locationBean) { ... } // Méthode appelée lorsqu'on soumet un formulaire HTML. public Response createLocation(@FormParam("address") String address, @FormParam("city") String city, @FormParam("nightPrice") Double price, @FormParam("zipCode") Integer zipCode) { ... }
Pour appeler la première méthode, il faut :
- Sélectionner l'onglet Body,
- cocher raw,
- sélectionner JSON ou XML,
- et enfin indiquer les données, au format JSON ou XML selon :
JSON{ "nightPrice": 654.3, "address": "64 avenue Jean Portalis", "city": "Tours", "zipCode": "37200" }
XML<locationBean> <nightPrice>654.3</nightPrice> <address>64 avenue Jean Portalis</address> <city>Tours</city> <zipCode>37200</zipCode> </locationBean>
Pour appeler la deuxième méthode, il faut :
- Sélectionner l'onglet Body,
- cocher x-www-form-urlencoded,
- et enfin indiquer les données, comme paramètre de la requête (
@FormParam
) :
-
Obtenir une erreur
415
si le format n'est pas le bon.Cela est automatique, à partir du moment où tout les format acceptés sont recensés explicitement dans les annotations
@Consumes
des deux méthodes de création.
Exercice 5 - Mise à jour d'une location⚓︎
Exercice 5 - Mise à jour d'une location
Implémenter les services permettant une mise à jour totale (6) et une mise à jour partielle (7) d'une location.
On implémentera que la partie concernant la mise à jour via des données envoyées en JSON ou en XML. On n'implémentera pas la partie concernant la mise à jour d'une location via un formulaire HTML.
Bonus : Pour ces services également, l'utilisateur doit être identifié.