• 8 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 18/02/2022

Renvoyez les bons codes et filtrez les réponses !

Renvoyez les bons codes HTTP

Quand vous avez testé votre méthode ajouterProduit, vous avez dû remarquer que celle-ci renvoie un code 200 OK une fois exécutée. Or, selon les standards du protocole HTTP, en cas de requête POST pour créer une ressource sur le serveur distant, il faut définir, dans l'en-tête de la réponse :

  • le code 201 Created ;

  • Si possible, l'URI vers la ressource créée dans le champ Location.

Mais c'est mon microservice, j'en fais ce que je veux !

Pas tout à fait ! Ce microservice est censé être consommé par d'autres services qui ne doivent surtout rien connaître, ni de son fonctionnement interne, ni de ses spécificités. Il faut que votre microservice respecte les conventions et les standards. En effet, s'il est nécessaire d'adapter les autres composants au comportement particulier d'un microservice, il perd tout intérêt.

Changeons donc ajouterProduit pour qu'elle renvoie le code de statut 201. Je vous présente le code complet, afin que vous preniez connaissance de l'ensemble des librairies à importer pour les objets que nous allons utiliser :

 package com.ecommerce.micrommerce.web.controller;

 import com.ecommerce.micrommerce.web.dao.ProductDao;

 import com.ecommerce.micrommerce.web.model.Product;

 import org.apache.coyote.Response;
import org.springframework.http.ResponseEntity;

       import org.springframework.web.bind.annotation.*;
       
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.util.List;
import java.util.Objects;

@RestController
public class ProductController {

  private final ProductDao productDao;

  public ProductController(ProductDao productDao) {
      this.productDao = productDao;
  }

  @GetMapping("/Produits")
  public List<Product> listeProduits() {
      return productDao.findAll();
      
  }

  @GetMapping(value = "/Produits/{id}")
  public Product afficherUnProduit(@PathVariable int id) {
      return productDao.findById(id);
  }

  @PostMapping(value = "/Produits")
  public ResponseEntity<Product> ajouterProduit(@RequestBody Product product) {
      Product productAdded = productDao.save(product);
      if (Objects.isNull(productAdded)) {
          return ResponseEntity.noContent().build();
      }
      URI location = ServletUriComponentsBuilder
              .fromCurrentRequest()
              .path("/{id}")
              .buildAndExpand(productAdded.getId())
              .toUri();
      return ResponseEntity.created(location).build();
  }
}

Le code de la méthode ajouterProduit est ici défini avec un code un peu plus élaboré que précédemment.

Tout d'abord, nous remplaçons la valeur de retour void  de la méthode ajouterProduit par ResponseEntity< Product>. ResponseEntity est une classe qui hérite de HttpEntity,  qui permet de définir le code HTTP  à retourner. L'intérêt de ResponseEntity est de nous donner la main pour personnaliser le code facilement.

Dans un premier temps, nous faisons appel à la DAO pour ajouter le produit. Dans le cas où le produit ajouté est vide ou n'existe pas, nous retournons le code 204 No Content. Pour cela, la méthode noContent() est utilisée.  Cette méthode est chaînée avec la méthode build() qui construit le header, et y ajoute le code choisi.

Dans le cas où tout s'est bien passé et que productAdded n'est donc pas null, nous avons besoin, en plus du code 201, d'ajouter l'URI vers cette nouvelle ressource créée, afin d'être conformes avec le protocole HTTP.

Nous déclarons donc une instance de la classe URI afin de la passer ensuite comme argument de ResponseEntity. Nous instancions cette URI à partir de l'URL de la requête reçue.

Nous ajoutons ensuite l'Id du produit à l'URI à l'aide de la méthode buildAndExpand. Nous retrouvons l'Id dans l'instance de Product que nous avons reçu : productAdded.getId().

Enfin, nous invoquons la méthode created de ResponseEntity, qui accepte comme argument l'URI de la ressource nouvellement créée, et renvoie le code de statut 201.

Voilà ! Redémarrez le microservice et rendez-vous dans Postman pour relancer la requête POST, comme illustré ci-dessous :

Renvoi du code 201 lors de l'ajout d'un produit
Renvoi du code 201 lors de l'ajout d'un produit

Nous constatons :

  1. En bas à droite de la figure, que le code de statut est bien 201 Created.

  2. Tout en bas de la figure, qu'il existe bien un champ Location dont la valeur est l'URL de la ressource créée.

Vous pouvez le vérifier en saisissant cette URL dans votre navigateur : vous obtiendrez alors un JSON contenant le produit que vous avez ajouté.

Filtrez les réponses de votre microservice

Jusqu'à maintenant, notre microservice va récupérer une ou des instances de Product, et les renvoyer au format JSON. Cependant, cela pose un problème : que se passe-t-il si mon produit comporte des informations sensibles que je ne veux pas exposer ? Par exemple, le prix d'achat du produit ou le nombre d'unités restantes en stock.

Pour résoudre ce problème, modifions la classe Product située dans le package model pour ajouter le prix d'achat du produit que nous ne souhaitons pas modifier. Voici la classe :

package com.ecommerce.microcommerce.model;



public class Product {



  private int id;

  private String nom;

  private int prix;



  //information que nous ne souhaitons pas exposer

  private int prixAchat;



  //constructeur par défaut

  public Product() {

  }



  //constructeur pour nos tests

  public Product(int id, String nom, int prix, int prixAchat) {

      this.id = id;

      this.nom = nom;

      this.prix = prix;

      this.prixAchat = prixAchat;


  }



  public int getId() {

      return id;

  }



  public void setId(int id) {

      this.id = id;

  }



  public String getNom() {

      return nom;

  }



  public void setNom(String nom) {

      this.nom = nom;

  }



  public int getPrix() {

      return prix;

  }



  public void setPrix(int prix) {

      this.prix = prix;

  }



  public int getPrixAchat() {

      return prixAchat;

  }



  public void setPrixAchat(int prixAchat) {

      this.prixAchat = prixAchat;

  }



  @Override

  public String toString() {

      return "Product{" +

            "id=" + id +

            ", nom='" + nom + '\'' +

            ", prix=" + prix +

            '}';

  }

}

N'oubliez pas d'ajouter les getters et setters, et de modifier le constructeur.

Rendez-vous ensuite dans ProductDaoImpl et ajoutez le dernier paramètre prixAchat manquant :

package com.ecommerce.micrommerce.web.dao;

import com.ecommerce.micrommerce.web.model.Product;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;

@Repository
public class ProductDaoImpl implements ProductDao{
  public static List<Product> products = new ArrayList<>();

  static {
      products.add(new Product(1, "Ordinateur portable", 350, 120));
      products.add(new Product(2, "Aspirateur Robot", 500, 200));
      products.add(new Product(3, "Table de Ping Pong", 750, 400));
  }

  @Override
  public List<Product> findAll() {
      return products;
  }

  @Override
  public Product findById(int id) {
      for (Product product : products){
          if (product.getId() == id){
              return product;
          }
      }
      return null;
  }

  @Override
  public Product save(Product product) {
      products.add(product);
      return product;
  }
}

Redémarrez votre application et appelez avec Postman l'URL http://localhost:9090/Produits. Vous obtenez cette réponse :

[

  {

      "id": 1,

      "nom": "Ordinateur portable",

      "prix": 350,

      "prixAchat": 120

  },

  {

      "id": 2,

      "nom": "Aspirateur Robot",

      "prix": 500,

      "prixAchat": 200

  },

  {

      "id": 3,

      "nom": "Table de Ping Pong",

      "prix": 750,

      "prixAchat": 400

  }

]

 

Aïe ! Votre prix d'achat est exposé à tous. Vos clients ne vont pas être contents d'apprendre que vous faites une marge si importante sur les ventes.

Utilisez le filtrage statique

Jackson, qui s'occupe de convertir les objets Java en JSON, vous offre une méthode toute simple. Ajoutez l'annotation @JsonIgnore au-dessus des propriétés que vous souhaitez cacher :

  //information que nous ne souhaitons pas exposer

 

  @JsonIgnore

 

  private int prixAchat;

Redémarrez votre microservice et bingo ! prixAchat a disparu !

Une autre notation qui peut être pratique quand vous avez beaucoup de choses à cacher... :

@JsonIgnoreProperties(value = {"prixAchat", "id"})

 

public class Product {

 

 ...

 

}

Il vous suffit d'entrer la liste des propriétés à ignorer. Dans ce cas, il n'y aura plus que le nom et le prix de chaque produit :

[

 

   {

 

      "nom": "Ordinateur portable",

 

      "prix": 350

 

   },

 

   {

 

      "nom": "Aspirateur Robot",

 

      "prix": 500

 

   },

 

   {

 

      "nom": "Table de Ping Pong",

 

      "prix": 750

 

   }

 

]

Utilisez le filtrage dynamique

La première méthode est simple et marchera dans pas mal de cas, mais elle est assez définitive.

Que faire si vous souhaitez afficher le prix d'achat ou non, en fonction de qui le demande ?

Par exemple, vous n'affichez jamais le prix d'achat à un service qui s'occupe de vendre le produit aux clients finaux, par contre vous exposez ce prix aux services qui s'occupent des calculs des bénéfices journaliers. Dans ce cas, il vous faut filtrer dynamiquement au cas par cas.

Commencez par enlever les annotations que nous avons appliquées précédemment à la classe Product, et remplacez-les par celles-ci :

@JsonFilter("monFiltreDynamique")

 

public class Product {

 

...

 

}

Cette annotation indique que ce Bean (Product) accepte un filtre qui porte le nom très créatif de monFiltreDynamique.

Il ne reste plus qu'à créer ce filtre. Rendez-vous sur votre contrôleur ProductController, et modifiez la méthode listeProduits comme suit :

//Récupérer la liste des produits
@GetMapping("/Produits")
public MappingJacksonValue listeProduits() {
   List<Product> produits = productDao.findAll();
   SimpleBeanPropertyFilter monFiltre = SimpleBeanPropertyFilter.serializeAllExcept("prixAchat");
   FilterProvider listDeNosFiltres = new SimpleFilterProvider().addFilter("monFiltreDynamique", monFiltre);
   MappingJacksonValue produitsFiltres = new MappingJacksonValue(produits);
   produitsFiltres.setFilters(listDeNosFiltres);
   return produitsFiltres;
}

Ce code stocke la liste des produits retournés par findAll dans une liste.

SimpleBeanPropertyFilter est une implémentation de PropertyFilter qui permet d'établir les règles de filtrage sur un Bean donné. Ici, nous avons choisi la règle serializeAllExcept qui exclut uniquement les propriétés que nous souhaitons ignorer. Inversement, vous pouvez procéder avec la méthode filterOutAllExcept qui marque toutes les propriétés comme étant à ignorer, sauf celles passées en argument.

Maintenant que nous avons établi notre règle de filtrage, la ligne suivante nous permet d'indiquer à Jackson à quel Bean l'appliquer. Nous utilisons SimpleFilterProvider pour déclarer que les règles de filtrage que nous avons créées (monFiltre) peuvent s'appliquer à tous les Beans qui sont annotés avec monFiltreDynamique.

Jusqu'à présent, nous n'avons encore rien filtré concrètement ! Nous avons établi la règle et indiqué que cette règle ne s'applique qu'aux Beans qui sont annotés avec monFiltreDynamique, mais nous ne l'avons pas encore appliquée. Pour cela, nous les mettons au format MappingJacksonValue. Cela permet de donner accès aux méthodes qui nous intéressent, comme setFilters qui applique les filtres que nous avons établis à la liste de Product. 

Nous retournons ensuite la liste filtrée. Ne vous inquiétez pas si vous devez renvoyer MappingJacksonValue, car ce n'est qu'un simple "conteneur" qui ne change absolument rien au contenu. MappingJacksonValue est donc exactement comme produits, avec des méthodes de filtrage en plus.

Il ne vous reste plus qu'à tester !

En résumé

  • Nous avons remplacé les types de retour pour rendre notre application cohérente avec la norme, grâce à ResponseEntity.

  • Nous savons appliquer des filtres sur notre API grâce à Jackson.

Afin de vérifier que tout est compris, que diriez-vous d’un petit quiz ?

Exemple de certificat de réussite
Exemple de certificat de réussite