Monday, 13 January, 2020 UTC


Summary

In this article, you’ll learn how to use WebSocket API with Spring Boot and build a simple status delivery application at the end.
WebSocket is a communication protocol that makes it possible to establish a two-way communication channel between a server and its client. Websockets are supported by most of the browsers that are commonly used today.
Create an Application
First, you need to set up your Twilio account and a suitable phone number.
  • If you haven't yet, sign up for a free Twilio Account
  • Purchase a phone number from the Console if you don't yet have one (you'll need one with SMS capability)
Here are the steps to generate a project using Spring Initializr:
  1. Go to http://start.spring.io/.
  2. Enter Artifact’s value as websocket-callback.
  3. Add Websocket in the dependencies section.
  4. Click Generate Project to download the project.
  5. Extract the downloaded zip file.
  6. Note: You will need Java 8 or later installed and download the JDK from here.

WebSocket Configuration

The first step is to configure the WebSocket endpoint and a message broker. Create a new class called 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();  }  } 
The @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.
In the first method configureMessageBroker, we’re configuring a message broker that will be used to route messages from one client to another.
The first line defines the messages whose destination starts with the/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.
The second line defines the messages whose destination starts with /app. After processing the message, the controller will send it to the broker channel.
In the second method registerStompEndpoints, we register a WebSocket endpoint, that the clients use to connect to our WebSocket server.
Notice the use of withSockJS() with the endpoint configuration. SockJS is used to enable fallback options for browsers that don’t support WebSocket.
The word STOMP in the method name and they're the derivations of Spring frameworks STOMP implementation. STOMP stands for Simple Text Oriented Messaging Protocol. It is a messaging protocol that defines the format and rules for data exchange.

Build with Gradle

Create a 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'  } } 

Create a resource representation class

To model the Message carrying SMS class, you can create a plain old Java object (POJO) with the 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 + '\'' +  '}';  } } 

Create a message-handling controller

In Spring’s approach to working with STOMP messaging, STOMP messages can be routed to @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());  } } 
This controller is concise and precise, but there are plenty of processes running internally. Let's review them step-by-step.
The @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.
The payload of the message is bound to a @RequestBody SMS object, which is passed into SMSSubmit().
The broadcast to all subscribers to /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.
Once the 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.
Similarly, the second method 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.

Create a Messaging Service

A component is a unit of software that is independently replaceable and upgradeable.
Microservice architectures will use libraries, but their primary way of componentizing their software is by breaking down into services. We define libraries as components that are linked into a program and called using in-memory function calls, and services as out-of-process components which communicate with a mechanism such as a web service request, or remote procedure call. One main reason for using services as components (rather than libraries) is that services are independently deployable. In the demo application, we have a service to communicate directly with the Twilio endpoints to send or receive callback information.
Twilio suggests you never save your credentials in your development environment or project. Instead, you can save these values and retrieve them through the ./bash_profile(environment) file on your development machine, or another suitable place depending on your operating system. Spring can extract data through externalized configuration.
We have a couple of blog posts on how to link the Environment Variables and Storing Credentials.
Below is the code of this implementation:
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) {  } } 

Create a browser client

Once all the server components are established and connected, the UI can be implemented with JS client and HTML pages. Recommend placing these files under src/main/resources/static.
Create an index.html file that looks like this:
<!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> 
This HTML file imports the 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.
Let’s create that file:
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(); 
Build an executable JAR
If you use Gradle, you can run the application by using ./gradlew bootRun.
Test the service
Now that the service is running, point your browser at http://localhost:8080 and click the "Send" button.
Upon opening a connection, you are asked for your to-number and message. Enter your information and click "Send". The data is sent to the server as a JSON message over STOMP. After a 1-second simulated delay, the server sends a message back with the callback status information received from Twilio.
Web sequence diagram of the complete implementation
End result of the implementation
Twilio with Spring Boot and WebSockets
If all the tests worked, you now have an SMS app built with Spring Boot and WebSockets. If you want to work alongside my code, you can find my repository here.
Let me know if you get it working - we can't wait to see what you build!

Further References:

  • Getting started using spring- https://spring.io/guides/gs/messaging-stomp-websocket/
  • Intro to WebSockets - https://www.baeldung.com/websockets-spring
  • Websocket support - https://docs.spring.io/spring-framework/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/websocket.html
  • using STOMP and SockJS -https://stomp-js.github.io/guide/stompjs/rx-stomp/ng2-stompjs/using-stomp-with-sockjs.html
Pooja Srinath is a Senior Solutions Engineer at Twilio. She's focused on learning new things and building cool apps to help budding developers. She can be found at psrinath [at] twilio.com.