この記事はPooja Srinathがこちらで公開した記事(英語)を日本語化したものです。
本稿では、Spring BootでWebSocket APIを使用し、シンプルなSMS送信アプリケーションを構築する方法について説明します。
WebSocketは、サーバーとクライアント間の双方向通信チャネルを確立する通信プロトコルです。WebSocketは、現在一般的に使用されているブラウザのほとんどにおいてサポートされています。
プロジェクトの設定
まず、Twilioアカウントの登録と電話番号を取得します。
Twilioのアカウントを登録します。Twilioホームページをブラウザで開き、[今すぐ無料サインアップ]ボタンをクリックするか、Twilioアカウントの作成リンクからサインアップします。このリンクを使用するとアカウントのアップグレード時に$10(米国ドル)相当分のクレジットが追加で付与されます。
次に、SMSの送信に使用する電話番号を購入します。
TwilioコンソールのBuy a Numberにアクセスします。
Countryで、電話番号が属する国を選択します。現在、TwilioではSMS送信機能付きの日本の電話番号は販売していませんので、米国等を選択してください。
各国の電話番号で使用できる機能について詳しくは、TwilioヘルプセンターのTwilioの国際電話番号の利用と機能(英語)を参照してください。
CapabilitiesでSMSがチェックされていることを確認してください。
購入した電話番号をコピーし、記録しておきます。
Spring Initializrを使いプロジェクトを生成します。手順は、次のとおりです。
- http://start.spring.io/にアクセスします。
- [Artifact]の値に「websocket-callback」と入力します。
- [Dependencies]セクションに「Websocket」を追加します。
- [Generate]をクリックし、プロジェクトをダウンロードします。
- ダウンロードしたZIPファイルを展開します。
Java 8以降をインストールし、JDKをこちらからダウンロードしてください。
WebSocketの設定
まずは、WebSocketエンドポイントとメッセージブローカーを設定します。WebSocketConfig
クラスをcom.twilio.websocketcallback
パッケージ内に作成し、以下の内容を入力します。
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
は、WebSocketサーバーの有効化に使用します。WebSocketMessageBrokerConfigurer
インタフェースを組み込み、WebSocket接続を設定するメソッドを指定します。
最初のメソッドのconfigureMessageBroker
では、クライアント間のメッセージルーティングに使用するメッセージブローカーを設定しています。
15行目では、宛先が/topic
で始まるメッセージをメッセージブローカーにルーティングするよう定義しています。メッセージブローカーは、特定のトピックを購読しているすべての接続クライアントにメッセージを配信します。
16行目では、宛先が/app
で始まるメッセージを定義しています。メッセージの処理後、コントローラーはメッセージをブローカーチャネルに送信します。
2番目のメソッドregisterStompEndpoints
では、クライアントがWebSocketサーバーへの接続に使用するWebSocketエンドポイントを登録しています。
エンドポイント設定にwithSockJS()
を使用しています。SockJSは、WebSocketをサポートしていないブラウザのフォールバックオプションを有効化するのに使用します。
メソッド名の「Stomp」は、SpringフレームワークのSTOMP実装が由来です。STOMPは、Simple Text Oriented Messaging Protocolの頭字語です。これは、データ交換の形式とルールを定義するメッセージングプロトコルです。
以下の図は、全二重通信チャネルを活用したWebSocketを表したものです。
Gradleを使い構築する
build.gradleファイルを作成し、以下のようにTwilioとWebSocketの依存ライブラリを含めます。
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'
}
}
リソース表現クラスを作成する
メッセージ送信SMSクラスをモデル化するには、to
とmessage
のプロパティと、関連するメソッドを持つPlain Old Java Object(POJO)を作成します。
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 + '\'' +
'}';
}
}
メッセージ処理コントローラーを作成する
STOMPメッセージングを使用するSpringのアプローチでは、STOMPメッセージを@Controller
クラスにルーティングできます。以下のようにSMSController
をマッピングして/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
アノテーションは、メッセージがPOSTメソッドを介して/sms
に送信されるようにします。コンシューマーが受け入れるcontent_typeは、consumes = MediaType.APPLICATION_JSON_VALUE
に設定されます。
produces = MediaType.APPLICATION_JSON_VALUE
は、マッピングされたハンドラーが生成する値です。produces
は、1つ以上のメディアタイプで構成されます。そのうちの1つは、リクエストの「受け入れ可能な」メディアタイプに対するコンテンツ交渉を介して選択する必要があります。通常、メディアタイプはAccept
ヘッダーから抽出されますが、クエリパラメーターや別の場所から抽出される場合もあります。
メッセージのペイロードは、@RequestBody SMS
オブジェクトにバインドされ、SMSSubmit()
に渡されます。
/topic/sms
の購読者全員への配信は、webSocket.convertAndSend()
メソッドにて指定しています。このメソッドは、アプリケーションからメッセージを、接続されたクライアントに送信するために使用されます。どのアプリケーションコンポーネントもメッセージを「brokerChannel」に送信できます。メッセージの送信は、SimpMessagingTemplate
注入により行われます。
message
フィールドとto
フィールドをUIから受け取ると、フィールド値が解析され、send
メソッドで定義するSMSサービスに送信されます。Twilio電話番号からUIに入力した電話番号に、SMSメッセージが送信されます。
smsCallback
メソッドは、Twilioエンドポイントからのコールバックを受信するコールバックハンドラーメソッドです。このメソッドは、MediaType.APPLICATION_FORM_URLENCODED_VALUE
タイプのリクエストcontent-type
ヘッダーを利用し、MediaType.APPLICATION_JSON_VALUE
タイプのネゴシエーション可能なmediatype
を生成します。このmediatype
を、フロントエンドのJavaScriptエンジンにより処理します。Twilioは、URL_ENCODEDフォーム値でコールバックを送信するため、アプリケーションで簡単に利用できるタイプの形式にしておく必要があります。
メッセージングサービスを作成する
コンポーネントは、単独で交換やアップグレードができるソフトウェアの単位です。
マイクロサービスアーキテクチャでは、ライブラリを使用しますが、ソフトウェアをコンポーネント化する主な方法は、サービスに分解することです。ライブラリはプログラムに関連付けられ、メモリ内の関数呼び出しで呼び出されるコンポーネントとして定義されます。また、サービスはWebサービスリクエストやリモートプロシージャコールなどの方法で通信するプロセス外のコンポーネントとして定義されます。サービスを(ライブラリではなく)コンポーネントとして使用する主な理由は、サービスが単独で展開できるからです。本稿で作成するアプリケーションでは、Twilioエンドポイントと直接通信するサービスを使用し、コールバック情報を送受信しています。
Twilioは、認証情報を開発環境やプロジェクト内に保存しないことを推奨しています。代わりに、開発マシンや、オペレーティングシステムに応じた別の場所にある./bash_profile
(環境)ファイルに認証情報を保存し、そこから取り出すようにしてください。Springでは、外部化設定を介してデータを抽出できます。
環境変数と認証情報を関連付ける方法については、以下のドキュメントを参照してください。
Store Your Twilio Credentials Securely (英語)
以下のコードで、メッセージングサービスを作成します。
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) {
}
}
ブラウザクライアントを作成する
すべてのサーバーコンポーネントの構築と接続が完了すると、JavaScriptクライアントとHTMLページを使用してUIを実装できます。src/main/resources/static
に、次のようなindex.htmlファイルを作成します。
<!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>
このHTMLファイルでは、SockJS
とSTOMP
のJavaScriptライブラリをインポートします。これらのライブラリは、WebSocket経由でSTOMPを使用したサーバーとの通信に使用されます。ここでは、クライアントアプリケーションのロジックが含まれたapp.js
ファイルもインポートしています。
そのファイルを作成しましょう。
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();
Gradleを使用している場合は、./gradlew bootRun
を使用し、アプリケーションを実行できます。
サービスをテストする
実行中のサービスをテストします。ブラウザでhttp://localhost:8080を開き、[Send]ボタンをクリックします。
接続が開始されると、宛先の電話番号とメッセージを入力するよう要求されます。情報を入力し、[Send]をクリックします。データは、STOMPを介してJSONとしてサーバーに送信されます。1秒間の遅延をシミュレーションした後、サーバーはTwilioから受信したコールバックステータス情報を含むメッセージを送り返します。
Spring BootとWebSocketでTwilioアプリケーションを構築する
すべてのテストが正常に完了すると、Spring BootとWebSocketで構築したSMSアプリケーションが完成します。本稿でご紹介したコードの全体を確認したい場合は、GitHubリポジトリを確認してください。
ぜひ、ご感想をお寄せください。コミュニケーションの未来を構築しましょう。
その他の参考資料(すべて英語):
Pooja Srinathは、Twilioのシニアソリューションエンジニアです。新しいことを学び、新進気鋭の開発者の助けとなる便利なアプリを構築することに力を入れています。ご不明な点は、psrinath@twilio.comまでお問い合わせください。

Learn how to build and test REST APIs using Java, Spring Boot, and Postman with a sample database on MySQL.

Learn how to navigate the Java IntelliJ IDEA to configure environment variables necessary for your project.

Environment variables let you configure applications without storing settings in code. In this short article, you'll learn two ways of working with them in Java.