WebSocketConfig inside com.twilio.websocketcallbackpackage with the following contents: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 is used to enable our WebSocket server. We implement WebSocketMessageBrokerConfigurer interface and provide an implementation for some of its methods to configure the WebSocket connection.configureMessageBroker, we’re configuring a message broker that will be used to route messages from one client to another./topic should be routed to the message broker. Message broker broadcasts messages to all the connected clients who are subscribed to a particular topic. Below is the pictorial representation of the WebSockets and the implementation details. /app. After processing the message, the controller will send it to the broker channel.registerStompEndpoints, we register a WebSocket endpoint, that the clients use to connect to our WebSocket server.withSockJS() with the endpoint configuration. SockJS is used to enable fallback options for browsers that don’t support WebSocket.build.gradle file with the associated Twilio and WebSockets dependency libraries. 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' } } to and message properties and the corresponding methods.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 + '\'' + '}'; } } @Controller classes. For example, the SMSController is mapped to handle messages to 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()); } } @RequestMapping annotation ensures that if a message is sent to the destination /sms through POST method. The content_type accepted by the consumer is set in the line consumes = MediaType.APPLICATION_JSON_VALUE.produces = MediaType.APPLICATION_JSON_VALUE is the value that can be produced by the mapped handler. produces consists of one or more media types, one of which must be chosen via content negotiation against the "acceptable" media types of the request. Typically those are extracted from the "Accept" header but may be derived from query parameters, or somewhere else. @RequestBody SMS object, which is passed into SMSSubmit()./topic/sms as specified in the webSocket.convertAndSend() method. This method is used to send messages to connected clients from any part of the application. Any application component can send messages to the"brokerChannel" and is done by SimpMessagingTemplate injection to send messages.message and to fields are received from the UI, these values are parsed and sent to the SMS service called send method. This will send outgoing SMS messages from your Twilio phone number to the text-capable phone number you input in the UI. Twilio sends SMS on-behalf of the actual wired phone number. With the programmable SMS, we can build logic to send SMS with a message body and the destination phone number and allow Twilio to do its magic.smsCallback is a callback handler method that receives the callback from the Twilio endpoint. It consumes a request content-type header of type MediaType.APPLICATION_FORM_URLENCODED_VALUE and produces a negotiable mediatype of type MediaType.APPLICATION_JSON_VALUE. This mediatype is then processed by our frontend js engine. Twilio sends a callback in URL_ENCODED form values, hence they need to be formatted into a type that is easily consumable by your application. ./bash_profile(environment) file on your development machine, or another suitable place depending on your operating system. Spring can extract data through externalized configuration. 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) { } } src/main/resources/static.<!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>
SockJS and STOMP javascript libraries that will be used to communicate with our server using STOMP over WebSocket. We’re also importing here an app.js file which contains the logic of our client application.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(); ./gradlew bootRun.