Envoyer des SMS dans votre application Spring Boot

January 13, 2020
Rédigé par

Envoyer des SMS dans votre application Spring Boot

Dans cet article, vous apprendrez à utiliser l'API WebSocket avec Spring Boot et, à la fin, à construire une application simple de fourniture d'état.

WebSocket est un protocole de communication qui permet d'établir un canal de communication bidirectionnel entre un serveur et son client. Les protocoles WebSockets sont pris en charge par la plupart des navigateurs couramment utilisés aujourd'hui. 

Créer une application

Tout d'abord, vous devez configurer votre compte Twilio et un numéro de téléphone approprié.

Voici les étapes pour générer un projet à l'aide de Spring Initializr : 

  1. Rendez-vous sur http://start.spring.io/.
  2. Entrez websocket-callback comme valeur d'Artifact.
  3. Ajoutez Websocket dans la section des dépendances.
  4. Cliquez sur Generate Project pour télécharger le projet.
  5. Extrayez le fichier zip téléchargé.
  6. Remarque : vous aurez besoin de Java 8 ou d'une version ultérieure et de télécharger le JDK ici.

Configuration de WebSocket

La première étape consiste à configurer le endpoint WebSocket et un Agent de messages. Créez une nouvelle classe appelée WebSocketConfig dans le package com.twilio.websoketcallback avec le contenu suivant :

package com.example.websocketcallback;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/gs-guide-websocket").withSockJS();
    }

}

@EnableWebSocketMessageBroker est utilisé pour activer notre serveur WebSocket. Nous incluons l'interface WebSocketMessageBrokerConfigurer et fournissons une implémentation pour certaines de ses méthodes de configuration de la connexion WebSocket.

Dans la première méthode configureMessageBroker, nous configurons un Agent de messages qui sera utilisé pour acheminer les messages d'un client à un autre.

La ligne 15 définit que les messages dont la destination commence par l'élément /topic doivent être acheminés vers l'Agent de messages. L'Agent de messages diffuse les messages à tous les clients connectés qui sont abonnés à un sujet particulier. 

La ligne 16 définit les messages dont la destination commence par l'élément /app. Une fois le message traité, le contrôleur l'envoie au canal de l'Agent.

Dans la deuxième méthode registerStompEndpoints, nous enregistrons un endpoint WebSocket que les clients utilisent pour se connecter à notre serveur WebSocket.

Notez l'utilisation de withSockJS() avec la configuration du endpoint. SockJS est utilisé pour activer les options de reprise pour les navigateurs qui ne prennent pas en charge WebSocket.

Le mot STOMP dans le nom de la méthode indique qu'il s'agit d'une dérivation de l'implémentation STOMP du framework Spring. STOMP est l'acronyme de Simple Text Oriented Messaging Protocol (Protocole de messagerie simple orienté texte). Il s'agit d'un protocole de messagerie qui définit le format et les règles d'échange de données.

Voici la représentation graphique des protocoles WebSockets qui facilite le canal de communication full-duplex :

Diagramme de la circulation des données dans l'application WebSocket

Construire avec Gradle

Créez un fichier build.gradle avec les bibliothèques de dépendance Twilio et WebSockets associées. 

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-websocket'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation group: "com.twilio.sdk", name: "twilio", version : "7.47.2"
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

Créer une classe de représentation de ressource

Pour modéliser la classe SMS de transport de message, vous pouvez créer un POJO (Plain Old Java Object, ou bon vieil objet Java) avec les propriétés to et message, et les méthodes correspondantes.

package com.twilio.websocketcallback.domain;

public class SMS {
    private String to;
    private String message;

    public String getTo() {
        return to;
    }

    public void setTo(String to) {
        this.to = to;
    }

    public String getMessage() {
        return message;
    }

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


    @Override
    public String toString() {
        return "SMS{" +
                "to='" + to + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}

Créer un contrôleur de gestion des messages

Dans l'approche de Spring pour travailler avec la messagerie STOMP, les messages STOMP peuvent être acheminés vers les classes @Controller. Par exemple, l'élément SMSController est mappé pour traiter les messages vers la destination /sms.

package com.example.websocketcallback;

import com.twilio.exception.ApiException;
import com.twilio.websocketcallback.domain.SMS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@RestController
public class SMSController {

    @Autowired
    SMSService service;

    @Autowired
    private SimpMessagingTemplate webSocket;

    private final String  TOPIC_DESTINATION = "/topic/sms";

    @RequestMapping(value = "/sms", method = RequestMethod.POST,
            consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public void smsSubmit(@RequestBody SMS sms) {
        try{
            service.send(sms);
        }
        catch(ApiException e){

            webSocket.convertAndSend(TOPIC_DESTINATION, getTimeStamp() + ": Error sending the SMS: "+e.getMessage());
            throw e;
        }
        webSocket.convertAndSend(TOPIC_DESTINATION, getTimeStamp() + ": SMS has been sent!: "+sms.getTo());

    }

    @RequestMapping(value = "/smscallback", method = RequestMethod.POST,
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public void smsCallback(@RequestBody MultiValueMap<String, String> map) {
       service.receive(map);
       webSocket.convertAndSend(TOPIC_DESTINATION, getTimeStamp() + ": Twilio has made a callback request! Here are the contents: "+map.toString());
    }

    private String getTimeStamp() {
       return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
    }
}

Ce contrôleur est concis et précis, mais de nombreux processus sont exécutés en interne. Passons-les en revue étape par étape.

L'annotation @RequestMapping garantit qu'un message est envoyé à l'élément /sms de destination via la méthode POST. L'élément content_type accepté par le consommateur est défini dans la ligne consumes = MediaType.APPLICATION_JSON_VALUE.

produces = MediaType.APPLICATION_JSON_VALUE est la valeur qui peut être produite par le gestionnaire mappé. produces se compose d'un ou de plusieurs types de média, dont l'un doit être choisi via négociation de contenu par rapport aux types de média « acceptables » de la requête. Généralement, ceux-ci sont extraits de l'en-tête "Accept" mais peuvent être dérivés des paramètres de requête, ou ailleurs. 

La charge utile du message est liée à un objet @RequestBody SMS, qui est transmis à SMSSubmit().

Diffusion à tous les abonnés à /topic/sms comme spécifié dans la méthode webSocket.convertAndSend(). Cette méthode est utilisée pour envoyer des messages aux clients connectés à partir de n'importe quelle partie de l'application. Tout composant d'application peut envoyer des messages au « canal d'Agent » et est exécuté par l'injection SimpMessagingTemplate pour envoyer des messages.

Une fois les champs message et to reçus de l'interface utilisateur, ces valeurs sont analysées et envoyées au service SMS appelé méthode send. Les SMS sortants de votre numéro de téléphone Twilio seront envoyés vers le numéro de téléphone compatible texte que vous avez saisi dans l'interface utilisateur. Twilio envoie des SMS pour le compte du numéro de téléphone filaire. Avec Programmable SMS, nous sommes en mesure de construire la logique pour envoyer des SMS avec un corps de message et le numéro de téléphone de destination, et permettre à Twilio de démontrer sa magie.

De même, la deuxième méthode smsCallback est une méthode de gestionnaire de rappel qui reçoit le rappel du endpoint Twilio. Elle consomme un en-tête de requête content-type de type MediaType.APPLICATION_FORM_URLENCODED_VALUE et produit un élément mediatype négociable de type MediaType.APPLICATION_JSON_VALUE. Cet élément mediatype est ensuite traité par notre moteur JS front-end. Twilio envoie un rappel dans les valeurs de format URL_ENCODED, elles doivent donc être formatées en un type facilement consommable par votre application. 

Créer un service de messagerie

Un composant est une unité logicielle qui peut être remplacée et mise à niveau indépendamment. 

Les architectures Microservice utiliseront des bibliothèques, mais leur principale façon de créer des composants pour leurs logiciels est de se décomposer en services. Nous définissons les bibliothèques comme des composants liés à un programme et appelés à l'aide d'appels de fonction en mémoire, et les services comme des composants hors processus qui communiquent avec un mécanisme tel qu'une requête de service Web ou un appel de procédure à distance. L'une des principales raisons d'utiliser les services comme composants (plutôt que les bibliothèques) est que les services sont déployables indépendamment. L'application de démonstration contient un service permettant de communiquer directement avec les points de terminaison Twilio pour envoyer ou recevoir des informations de rappel.

Twilio vous suggère de ne jamais enregistrer vos identifiants dans votre environnement de développement ou votre projet. Au lieu de cela, vous pouvez enregistrer ces valeurs et les récupérer via le fichier ./bash_profile (environnement) sur votre machine de développement, ou un autre emplacement approprié selon votre système d'exploitation. Spring peut extraire des données via une configuration externalisée

Nous avons quelques billets de blog sur la façon de lier les variables d'environnement et le stockage des identifiants.

Vous trouverez ci-dessous le code de cette implémentation :

package com.example. websocketcallback;
import com.twilio. websocketcallback.domain.SMS;
import com.twilio.rest.api.v2010.account.Message;
import com.twilio.type.PhoneNumber;
import com.twilio.Twilio;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;

import java.net.URI;

@Component
public class SMSService {

    @Value("#{systemEnvironment['TWILIO_ACCOUNT_SID']}")
    private String ACCOUNT_SID;

    @Value("#{systemEnvironment['TWILIO_AUTH_TOKEN']}")
    private String AUTH_TOKEN;

    @Value("#{systemEnvironment['TWILIO_PHONE_NUMBER']}")
    private String FROM_NUMBER;

    public void send(SMS sms) {
        Twilio.init(ACCOUNT_SID, AUTH_TOKEN);

        Message message = Message.creator(new PhoneNumber(sms.getTo()), new PhoneNumber(FROM_NUMBER), sms.getMessage())
                .create();
        System.out.println("here is my id:"+message.getSid());// Unique resource ID created to manage this transaction

    }

    public void receive(MultiValueMap<String, String> smscallback) {
    }
}

Créer un client javascript

Une fois tous les composants du serveur établis et connectés, l'interface utilisateur peut être implémentée avec le client JS et les pages HTML. Il est recommandé de placer ces fichiers sous src/main/resources/static.

Créez un fichier index.html qui ressemble à ceci :

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Hello WebSocket</title>

    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
    <link href="/main.css" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.4.0/sockjs.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.js"></script>
    <script src="/app.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
        <div class="col-md-6">

            <form class="form-inline" id ="smsForm"  method="post">
                <div class="form-group">
                <p>To Number: <input type="text"  id="to" /></p>
                    <br/><p>Message: <textarea  id="message" > </textarea></p>
                </div>
                <p> <button id="send" class="btn btn-primary" type="submit">Send</button></p>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Message Status dashboard</th>
                </tr>
                </thead>
                <tbody id="messages">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

Ce fichier HTML importe les bibliothèques javascript SockJS et STOMP qui seront utilisées pour communiquer avec notre serveur à l'aide de STOMP sur WebSocket. Nous importons également ici un fichier app.js qui contient la logique de notre application client.

Créons ce fichier :

var stompClient = null;

function setConnected(connected) {

    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#messages").html("");
}

function connect() {
    var socket = new SockJS('/gs-guide-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/sms', function (sms) {
            showData(sms.body);
        });
    });
}

function disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
    }
    setConnected(false);
    console.log("Disconnected");
}

function showData(message) {
    $("#messages").append("<tr><td>" + message + "</td></tr>");
}

function processForm(e) {
    if (e.preventDefault) e.preventDefault();
    var form = document.getElementById('smsForm');
    var data = {};
    for (var i = 0, ii = form.length; i < ii -1; ++i) {
        var input = form[i];
        if (input.id) {
            data[input.id] = input.value;
        }
    }
    // construct an HTTP request
    var xhr = new XMLHttpRequest();
    xhr.open("post", "/sms", true);
    xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');

    // send the collected data as JSON
    xhr.send(JSON.stringify(data));

    return false;
}

$(function () {

    let form = document.getElementById('smsForm');
    if (form.attachEvent) {
        form.attachEvent("submit", processForm);

    } else {
        form.addEventListener("submit", processForm);
    }

        // collect the form data while iterating over the inputs

    });

    connect();

Créez un fichier JAR exécutable

Si vous utilisez Gradle, vous pouvez exécuter l'application à l'aide de ./gradlew bootRun

Tester le service

Maintenant que le service est en cours d'exécution, pointez votre navigateur sur http://localhost:8080 et cliquez sur le bouton « Send » (Envoyer).

Lors de l'ouverture d'une connexion, vous êtes invité à saisir votre numéro de téléphone et votre message. Entrez vos informations et cliquez sur « Send » (Envoyer). Les données sont envoyées au serveur sous forme de message JSON via STOMP. Après un délai simulé de 1 seconde, le serveur renvoie un message avec les informations d'état de rappel reçues de Twilio. 

schéma du fonctionnement de Twilio

Diagramme de séquence Web de l'implémentation complète

Résultat final de l&#x27;implémentation

Résultat final de l'implémentation

Twilio avec Spring Boot et WebSockets

Si tous les tests ont fonctionné, vous disposez maintenant d'une application SMS construite avec Spring Boot et WebSockets. Si vous voulez travailler en suivant mon code, vous trouverez mon repo git ici.

Faites-moi savoir si tout fonctionne comme prévu. 

J’ai hâte de voir ce que vous allez construire !

Autres références :

Pooja Srinath est ingénieur Solutions senior chez Twilio. Elle se concentre sur l'apprentissage de nouvelles connaissances et la construction d'applications intéressantes pour aider les développeurs en herbe. Vous pouvez la retrouver sur psrinath [at] twilio.com.