• 20 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 17/08/2018

Renvoyez les bons codes et filtrez les réponses !

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

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 connaitre, 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 :p.

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 objects que nous allons utiliser :

package com.ecommerce.microcommerce.web.controller;

import com.ecommerce.microcommerce.dao.ProductDao;
import com.ecommerce.microcommerce.model.Product;
import org.springframework.beans.factory.annotation.Autowired;
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;

@RestController
public class ProductController {

    @Autowired
    private ProductDao productDao;


    //Récupérer la liste des produits
    @RequestMapping(value = "/Produits", method = RequestMethod.GET)

    public List<Product> listeProduits() {
        return productDao.findAll();
    }


    //Récupérer un produit par son Id
    @GetMapping(value = "/Produits/{id}")

    public Product afficherUnProduit(@PathVariable int id) {

        return productDao.findById(id);
    }

    //ajouter un produit
    @PostMapping(value = "/Produits")
    public ResponseEntity<Void> ajouterProduit(@RequestBody Product product) {

        Product productAdded =  productDao.save(product);

        if (productAdded == null)
            return ResponseEntity.noContent().build();

        URI location = ServletUriComponentsBuilder
                .fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(productAdded.getId())
                .toUri();

        return ResponseEntity.created(location).build();
    }


}

Explication

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< Void >. ResponseEntity est une classe qui hérite de HttpEntity,  qui permet de définir le code HTTP  à retourner. L'interê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 chainé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 conforme 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 :

Figure 50
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 +
                '}';
    }
}

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

package com.ecommerce.microcommerce.dao;

import com.ecommerce.microcommerce.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, new String("Ordinateur portable"), 350, 120));
        products.add(new Product(2, new String("Aspirateur Robot"), 500, 200));
        products.add(new Product(3, new String("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
    }
]

Aie ! 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 ;).

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
    }
]

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 listeProuits comme suit :

//Récupérer la liste des produits
   @RequestMapping(value = "/Produits", method = RequestMethod.GET)

   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;
   }

Explications

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 Bean 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 Bean 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 :) !

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