DéveloppeurWeb.Com
    DéveloppeurWeb.Com
    • Agile Zone
    • AI Zone
    • Cloud Zone
    • Database Zone
    • DevOps Zone
    • Integration Zone
    • Web Dev Zone
    DéveloppeurWeb.Com
    Home»Java Zone»Une application Java MicroProfile pour la négociation de contenu
    Java Zone

    Une application Java MicroProfile pour la négociation de contenu

    octobre 20, 2021
    Une application Java MicroProfile pour la négociation de contenu
    Share
    Facebook Twitter Pinterest Reddit WhatsApp Email

    Un serveur HTTP peut utiliser la négociation de contenu pour répondre à différents clients. Les clients modernes attendent généralement une réponse JSON. Parfois, un format différent est requis, tel que XML pour les clients plus anciens ou binaire pour les plus récents. Le processus utilisé pour gérer ce défi, ainsi que d’autres comme la gestion de nombreuses langues et même la compression des requêtes HTTP, est connu sous le nom de négociation de contenu.

    Dans cet article, nous allons parcourir le processus de création d’une petite application Java MicroProfile tout en expliquant le fonctionnement de la négociation de contenu.

    Conditions préalables

    Si vous voulez aller de l’avant, la source de cet exemple se trouve sur GitHub.

    Qu’est-ce que MicroProfile ?

    Vers 2016, les spécifications Java EE avaient commencé à stagner et souffraient de longs cycles de publication ; en conséquence, les nouvelles API de services Web prenaient du retard. Créé pour stimuler l’innovation, le nouveau projet MicroProfile répondait au besoin de gérer les API Java JAX-RS, CDI et JSON-P existantes, ainsi que d’autres nouvelles API pour le monde en évolution des micro-services.

    Avance rapide jusqu’à aujourd’hui, Java EE a déménagé à Jakarta EE sous la fondation Eclipse et de nombreux projets MicroProfile sont devenus des projets Jakarta EE.

    Certains projets modifient les packages racine au fur et à mesure qu’ils se déplacent entre les projets. Tout ce qui commence par javax.* peut avoir été déplacé vers jakarta.* ou org.eclipse.*.

    Qu’est-ce que la négociation de contenu ?

    Dans la négociation de contenu pilotée par le serveur, le client envoie une requête à un serveur avec des instructions sur le type de réponse qu’il peut gérer. Lorsque cela est possible, le serveur répond avec le format approprié ou renvoie un 406 ou 415 code d’état.

    À un niveau élevé, la conversation ressemble à ceci :

    Client:
    Salut serveur !
    j’aimerais regarder https://api.example.com/user/123.
    J’ai besoin de votre réponse en JSON.

    Serveur:
    Client sans problème ! Voici cette réponse…

    Un autre exemple plus détaillé pourrait être :
    Client:
    Salut serveur ! j’ai besoin de https://api.example.com/user/123, de préférence en JSON !
    Mais, je vais prendre XML si c’est tout ce que vous avez.
    J’ai aussi besoin de l’info en anglais ou en français.
    Oh, veuillez également compresser le contenu.

    GET /user/123 HTTP/1.1
    Accept: application/json,application/xml;q=0.9
    Accept-Encoding: gzip
    Accept-Language: en,fr
    Host: api.example.com
    User-Agent: Client/2.0

    Serveur:
    Hé cliente !
    Tout ce que j’ai c’est XML (désolé), la réponse est en anglais,
    et j’ai pu le zip; Voici…

     					HTTP/1.1 200 OK
                                            Content-Type: application/xml
                                            Content-Encoding: gzip
                                            Content-Language: en
    
                                            <user id="123">
                                              ...
                                            </user>

    J’aimerais profiter de cette occasion pour m’excuser d’avoir utilisé XML dans cet exemple

    La négociation de contenu pilotée par agent fonctionne différemment. Dans ce cas, le client doit déjà connaître les capacités du serveur ou doit les déterminer en adressant une requête au serveur. Cette communication n’est pas standardisée.

    En-têtes de négociation de contenu

    Dans l’exemple précédent, j’ai utilisé les trois en-têtes de négociation de contenu :

    • Accept – La liste des types de médias pris en charge par le client.

    • Accept-Encoding – La liste des algorithmes de compression pris en charge par le client.

    • Accept-Language – La liste des langues prises en charge par le client.

    Ces en-têtes « accept » permettent une « valeur de qualité » ou une facteur q pour définir les préférences du client. En cas d’omission, la valeur par défaut est 1.0. L’exemple ci-dessus de application/json,application/xml;q=0.9 indique au serveur que JSON est préféré, mais XML serait le prochain choix.

    La réponse du serveur contient des en-têtes pour indiquer au client quelles options ont été sélectionnées.

    • Content-Type – Le type de média contenu dans la réponse.

    • Content-Encoding – L’algorithme de compression utilisé.

    • Content-Language – La langue de la réponse.

    Lorsque j’examine la demande que mon navigateur a faite pour afficher cet article de blog, cela ressemble à ceci :

    J’accepte

    text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/échange-signé;v=b3;q=0.9

    Accepter-Encodage

    gzip, dégonfler, br

    Accepter-Langue

    en-US, en; q = 0.9

    Mon navigateur dit au serveur : je prends n’importe quoi, mais voici une liste ordonnée de ce que je préfère :

    1. HTML, XHTML, AVIF, WebP ou APNG

    2. XML ou un échange signé

    3. Rien d’autre.

    Le navigateur prend en charge gzip, deflate, et br compression. Enfin, ma préférence linguistique est l’anglais américain (comme dans la couleur orthographiée sans « u ») ou n’importe quel anglais.

    Il y a un inconvénient. Ces en-têtes peuvent également être utilisés pour le suivi du navigateur et l’empreinte digitale.

    Assez d’introduction, passons au code !

    Créer un projet MicroProfile avec Quarkus

    La plupart du code ci-dessous doit être indépendant du fournisseur, sauf indication contraire. Essayez l’exemple avec votre fournisseur MicroProfile préféré et dites-moi comment cela s’est passé dans les commentaires !

    Créer un nouveau projet :

    mvn io.quarkus:quarkus-maven-plugin:2.0.2.Final:create 
        -DprojectGroupId=com.example 
        -DprojectArtifactId=content-negotation 
        -DclassName="com.example.DiceResource" 
        -Dextensions="resteasy-jsonb"
        -Dpath="/roll"
    cd content-negotation

    Si vous ne l’avez pas deviné, cet exemple va évaluer la notation des dés. La bibliothèque Dice Notation Parser évaluera les expressions de dés telles que 2d8+2 et renvoie les résultats. Ouvrez votre pom.xml fichier et ajoutez le dice-parser dépendance:

    <dependency>
        <groupId>dev.diceroll</groupId>
        <artifactId>dice-parser</artifactId>
        <version>0.1.0</version>
    </dependency>

    Mettre à jour le DiceResource classe dans src/main/java/com/example/resources d’ajouter un GET méthode:

    package com.example.resources;
    
    import dev.diceroll.parser.ParseException;
    import dev.diceroll.parser.ResultTree;
    
    import javax.ws.rs.*;
    import javax.ws.rs.core.*; // wildcard for brevity
    
    import static dev.diceroll.parser.Dice.detailedRoll;
    
    @Path("/roll")
    public class DiceResource  {
    
        @GET
        public ResultTree rollObject(@QueryParam("dice") String dice) throws ParseException {
            return detailedRoll(dice);
        }
    }

    Démarrez le serveur depuis votre IDE préféré ou depuis la ligne de commande avec :

    Toutes les modifications de code que vous apporterez devrait rechargement à chaud, mais si vous ne voyez pas les changements au fur et à mesure que vous continuez, arrêtez simplement le processus et redémarrez-le.

    Assurez-vous que les choses fonctionnent en faisant une demande et lancez un seul dé à six faces.

    HTTP/1.1 200 OK
    Content-Length: 148
    Content-Type: application/json
    
    {
        "expression": {
            "numberOfDice": 1,
            "numberOfFaces": 6
        },
        "results": [{
            "expression": {
                "numberOfDice": 1,
                "numberOfFaces": 6
            },
            "results": [],
            "value": 6
        }],
        "value": 6
    }
    

    Maintenant que l’application fonctionne, modifions certaines choses et regardons comment la réponse change !

    Activer la compression pour les ressources REST

    La compression n’est pas l’une de ces fonctionnalités dont vous devriez vous soucier. Tu pourrait gérez vous-même la logique de compression, mais la plupart des fournisseurs ont une propriété de configuration que vous pouvez modifier pour l’activer.

    Pour Quarkus, ajoutez la ligne suivante à votre src/main/resources/application.properties:

    quarkus.http.enable-compression=true
    

    Faites une autre requête HTTP et incluez le Accept-Encoding en-tête, et cette fois nous allons rouler 2d6:

    http :8080/roll dice==2d6 "Accept-Encoding: gzip"

    HTTP/1.1 200 OK
    Content-Type: application/json
    Content-Encoding: gzip 
    Content-Length: 106
    
    {
        "expression": {
            "numberOfDice": 2,
            "numberOfFaces": 6
        },
        ...
        "value": 9
    }

    L’en-tête Content-Encoding de la réponse a été défini (HTTPie a automatiquement décompressé la demande)

    Si vous vouliez faire la même chose avec curl vous auriez besoin de rediriger le résultat vers gunzip :

    curl localhost:8080/roll?dice=2d6 -H "Accept-Encoding: gzip" | gunzip
    

    Utilisation de l’en-tête Request Accept-Language

    Nous sommes en 2021 et la mise en œuvre de l’internationalisation (i18n) est encore difficile. Dans le monde Java, i18n signifie généralement créer un Locale objet contenant la langue de l’utilisateur. C’est un peu maladroit à utiliser, mais l’API JAX-RS définit un moyen de résoudre le problème de l’utilisateur Locale. Regardons quelques façons différentes de gérer cela.

    Ajouter une nouvelle méthode de point de terminaison dans DiceResource:

    @Path("/lang")
    @GET
    public Response getLang(@Context Request request) {
        List<Variant> variants = Variant.VariantListBuilder.newInstance() 
                .languages(Locale.ENGLISH, Locale.GERMAN) 
                .build();
    
        Variant variant = request.selectVariant(variants); 
    
        if (variant == null) { 
            return Response.notAcceptable(variants).build();
        }
    
        // set the response header, to the client knows which language was selected
        String lang = variant.getLanguageString();
        return Response.ok(lang)
                .header(HttpHeaders.CONTENT_LANGUAGE, lang)
                .build(); 
    }

    1. Utilisez le générateur de liste de variantes pour sélectionner la langue dans l’en-tête « Accepter les langues ».
    2. Vous devrez lister toutes vos langues prises en charge ; l’ordre est important ; l’option par défaut est la première s’il n’y a pas d’en-tête Accept-Language.
    3. Utilisez la demande pour sélectionner la bonne variante.
    4. Si la variante sélectionnée est nulle, retournez un 406.
    5. Créez et renvoyez une réponse 200.

    Le VariantBuilder prend également en charge différents encodages et MediaType.

    Essaye le! Faire une demande à /roll/lang:

    http :8080/roll/lang "Accept-Language: de"
    

    HTTP/1.1 200 OK
    Content-Language: de 
    Content-Type: text/plain;charset=UTF-8
    Vary: Accept-Language
    content-encoding: gzip
    content-length: 28
    
    de

    1. Noter la Content-Language entête.

    L’approche ci-dessus fonctionne bien pour montrer l’API, mais elle est un peu limitée dans les utilisations réelles car chaque méthode de point de terminaison renvoie un Response et gère directement les en-têtes. Il serait plus agréable d’extraire cette préoccupation transversale.

    Une autre option consiste à utiliser des filtres de demande et de réponse. L’exemple suivant implémente les deux ContainerRequestFilter et ContainerResponseFilter interfaces. Créer une nouvelle classe LanguageFilter:

    package com.example;
    
    import javax.ws.rs.container.*;
    import javax.ws.rs.core.*; // wildcard for brevity
    import javax.ws.rs.ext.Provider;
    import java.util.List;
    import java.util.Locale;
    
    @Provider
    public class LanguageFilter implements ContainerRequestFilter, ContainerResponseFilter {
    
        final private static String LANG = "LanguageFilter.lang";
    
        final public static List<Variant> VARIANTS = Variant.VariantListBuilder.newInstance()
                .languages(Locale.ENGLISH, Locale.GERMAN) 
                .build();
    
        @Override
        public void filter(ContainerRequestContext requestContext) {
            Variant variant = requestContext.getRequest().selectVariant(VARIANTS); 
    
            if (variant == null) { 
                // Error, respond with 406
                requestContext.abortWith(Response.notAcceptable(VARIANTS).build());
            } else {
                // keep the resolved lang around for the response
                requestContext.setProperty(LANG, variant.getLanguageString()); 
            }
        }
    
        @Override
        public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
            String lang = (String) requestContext.getProperty(LANG);
            responseContext.getHeaders().putSingle(HttpHeaders.CONTENT_LANGUAGE, lang); 
        }
    }

    1. Définissez les langues prises en charge.
    2. Sélectionnez la variante en fonction de la demande.
    3. Si un…
    Share. Facebook Twitter Pinterest LinkedIn WhatsApp Reddit Email
    Add A Comment

    Leave A Reply Cancel Reply

    Catégories

    • Politique de cookies
    • Politique de confidentialité
    • CONTACT
    • Politique du DMCA
    • CONDITIONS D’UTILISATION
    • Avertissement
    © 2023 DéveloppeurWeb.Com.

    Type above and press Enter to search. Press Esc to cancel.