WebSocketアプリケーションの作成
KotlinとKtorでWebSocketアプリケーションを作成する
コード例: tutorial-server-websockets
使用するプラグイン:
この記事では、KotlinとKtorを使用してWebSocketアプリケーションを作成するプロセスを説明します。これは、
この記事では、以下の方法について説明します:
- JSONシリアライズを使用するサービスの作成。
- WebSocket接続を介したコンテンツの送信と受信。
- 複数のクライアントへのコンテンツの同時ブロードキャスト。
前提条件
このチュートリアルは単独で行うこともできますが、
IntelliJ IDEAのインストールを推奨しますが、お好みの他のIDEを使用することも可能です。
Hello WebSockets
このチュートリアルでは、
Taskオブジェクトをやり取りする機能を追加します。これを実現するには、プラグインを含む初期プロジェクトの作成
Ktor Project Generatorにアクセスします。
Project artifactフィールドに、プロジェクトのアーティファクト名として com.example.ktor-websockets-task-app と入力します。

プラグインセクションで、以下のプラグインを検索し、Addボタンをクリックして追加します:
- Content Negotiation
- kotlinx.serialization
- WebSockets
- Static Content

プラグインを追加すると、プラグインセクションの右上に表示されます。
プロジェクトに追加されるすべてのプラグインのリストが表示されます:

Downloadボタンをクリックして、Ktorプロジェクトを生成し、ダウンロードします。
スターターコードの追加
ダウンロードが完了したら、IntelliJ IDEAでプロジェクトを開き、以下の手順に従います:
- src/main/kotlinに移動し、modelという新しいサブパッケージを作成します。
modelパッケージ内に、新しいTask.ktファイルを作成します。
Task.ktファイルを開き、優先度を表す
enumと、タスクを表すdata classを追加します:kotlinTaskクラスには、kotlinx.serializationライブラリのSerializable型のアノテーションが付いていることに注意してください。これは、インスタンスをJSONとの間で変換でき、その内容をネットワーク経由で転送できることを意味します。WebSocketsプラグインを含めたため、ジェネレーターによってsrc/main/kotlin内のWebsockets.ktファイルと、Routing.ktファイルに
webSocketルートが追加されています。- Websockets.ktファイルを開き、既存の
.configureWebsockets()関数を次のように置き換えます:kotlin- WebSocketsプラグインがインストールされ、標準設定で構成されます。
contentConverterプロパティが設定され、プラグインがkotlinx.serializationライブラリを通じて送受信されるオブジェクトをシリアライズできるようになります。
Routing.ktファイルを開き、既存の
Application.configureRouting()関数を以下の実装に置き換えます:kotlin- ルーティングは、相対URLが
/tasksである単一のエンドポイントで構成されます。 - リクエストを受信すると、タスクのリストがWebSocket接続を介してシリアライズされて送信されます。
- すべてのアイテムが送信されると、サーバーは接続を閉じます。
デモンストレーション目的で、タスクの送信間に1秒の遅延が導入されています。これにより、クライアントでタスクが段階的に表示される様子を観察できます。この遅延がない場合、この例は以前の記事で開発した
RESTfulサービスやKotlinとKtorを使用してバックエンドサービスを構築する方法を学びます。JSONファイルを生成するRESTful APIの例が含まれています。Webアプリケーションと同じように見えてしまいます。Kotlin、Ktor、Thymeleafテンプレートを使用してWebサイトを構築する方法を学びます。このイテレーションの最後のステップは、このエンドポイント用のクライアントを作成することです。
Static Contentプラグインを含めたため、Ktorプロジェクトジェネレーターによってsrc/main/resources/static内にindex.htmlファイルが追加されています。スタイルシート、スクリプト、画像などの静的コンテンツを提供する方法を学びます。- ルーティングは、相対URLが
index.htmlファイルを開き、既存の内容を以下のように置き換えます:
htmlこのページでは、すべての最新ブラウザで使用可能な
WebSocket型を使用しています。JavaScriptでこのオブジェクトを作成し、コンストラクタにエンドポイントのURLを渡します。その後、onopen、onclose、およびonmessageイベントのイベントハンドラーをアタッチします。onmessageイベントがトリガーされると、documentオブジェクトのメソッドを使用してテーブルに行を追加します。IntelliJ IDEAで実行ボタン (
) をクリックしてアプリケーションを起動します。
http://0.0.0.0:8080/static/index.htmlにアクセスします。ボタンのあるフォームと空のテーブルが表示されるはずです:

フォームをクリックすると、サーバーからタスクが読み込まれ、1秒間に1つのペースで表示されます。その結果、テーブルには段階的にデータが入力されます。ブラウザのデベロッパーツールでJavaScriptコンソールを開くと、ログメッセージも確認できます。

これで、サービスは期待どおりに動作しています。WebSocket接続が開かれ、アイテムがクライアントに送信され、接続が閉じられます。基礎となるネットワークには多くの複雑さがありますが、Ktorはデフォルトでこれらすべてを処理します。
WebSocketを理解する
次のイテレーションに進む前に、WebSocketの基本をいくつか確認しておくと役立つかもしれません。WebSocketにすでに精通している場合は、サービスの設計改善に進んでかまいません。
これまでのチュートリアルでは、クライアントはHTTPリクエストを送信し、HTTPレスポンスを受信していました。これはうまく機能し、インターネットのスケーラビリティと耐障害性を可能にしています。
しかし、以下のようなシナリオには適していません:
- コンテンツが時間の経過とともに段階的に生成される。
- イベントに応じてコンテンツが頻繁に変更される。
- コンテンツが生成される際にクライアントがサーバーと対話する必要がある。
- 1つのクライアントによって送信されたデータを他のクライアントに迅速に伝播させる必要がある。
これらのシナリオの例としては、株取引、映画やコンサートのチケット購入、オンラインオークションでの入札、ソーシャルメディアのチャット機能などがあります。WebSocketは、これらの状況に対処するために開発されました。
WebSocket接続はTCP上で確立され、長期間持続させることができます。接続は全二重通信(full duplex communication)を提供します。つまり、クライアントはサーバーにメッセージを送信し、同時にサーバーからメッセージを受信することができます。
WebSocket APIは、4つのイベント(open、message、close、error)と2つのアクション(send、close)を定義しています。この機能へのアクセス方法は、言語やライブラリによって異なります。例えば、Kotlinでは、着信メッセージのシーケンスをFlowとして利用できます。
設計の改善
次に、より高度な例に対応できるように既存のコードをリファクタリングします。
modelパッケージ内に、新しいTaskRepository.ktファイルを作成します。
TaskRepository.ktを開き、
TaskRepository型を追加します:kotlinこのコードは、以前のチュートリアルで見た覚えがあるかもしれません。
- src/main/kotlinに移動し、Routing.ktファイルを開きます。
TaskRepositoryを利用することで、Application.configureRouting()のルーティングを簡素化できます:kotlin
WebSocket経由でメッセージを送信する
WebSocketのパワーを説明するために、次のような新しいエンドポイントを作成します:
- クライアントが起動すると、既存のすべてのタスクを受信します。
- クライアントはタスクを作成して送信できます。
- 1つのクライアントがタスクを送信すると、他のクライアントに通知されます。
Routing.ktファイル内の現在の
.configureRouting()メソッドを以下の実装に置き換えます:kotlinこのコードで以下のことを行いました:
- 既存のすべてのタスクを送信する機能をヘルパーメソッドにリファクタリングしました。
routing {}ブロック内で、すべてのクライアントを追跡するためのスレッドセーフなsessionオブジェクトのリストを作成しました。- 相対URLが
/tasks2の新しいエンドポイントを追加しました。クライアントがこのエンドポイントに接続すると、対応するsessionオブジェクトがリストに追加されます。その後、サーバーは新しいタスクの受信を待つ無限ループに入ります。新しいタスクを受信すると、サーバーはそれをリポジトリに保存し、現在のクライアントを含むすべてのクライアントにコピーを送信します。
この機能をテストするために、index.htmlの機能を拡張した新しいページを作成します。
src/main/resources/static内に、wsClient.htmlという新しいHTMLファイルを作成します。
wsClient.htmlを開き、以下の内容を追加します:
htmlこの新しいページには、ユーザーが新しいタスクの情報を入力できるHTMLフォームが導入されています。フォームを送信すると、
sendTaskToServer()イベントハンドラーが呼び出されます。これにより、フォームデータを使用してJavaScriptオブジェクトが構築され、WebSocketオブジェクトの.send()メソッドを使用してサーバーに送信されます。IntelliJ IDEAで、再実行ボタン (
) をクリックしてアプリケーションを再起動します。
この機能をテストするには、2つのブラウザを並べて開き、以下の手順に従います。
- ブラウザAで、http://0.0.0.0:8080/static/wsClient.htmlにアクセスします。デフォルトのタスクが表示されるはずです。
- ブラウザAで新しいタスクを追加します。新しいタスクがそのページのテーブルに表示されるはずです。
- ブラウザBで、http://0.0.0.0:8080/static/wsClient.htmlにアクセスします。デフォルトのタスクに加えて、ブラウザAで追加した新しいタスクも表示されるはずです。
- どちらかのブラウザでタスクを追加します。両方のページに新しいアイテムが表示されるはずです。

自動テストの追加
QAプロセスを効率化し、高速、再現可能、かつハンズフリーにするために、Ktorに組み込まれている
Ktor Client内で
Content Negotiationのサポートを構成できるように、以下の依存関係をbuild.gradle.ktsに追加します:ContentNegotiationプラグインは、クライアントとサーバー間のメディアタイプのネゴシエーションと、特定の形式でのコンテンツのシリアライズ/デシリアライズという2つの主な目的を果たします。kotlinIntelliJ IDEAで、エディターの右側にあるGradle通知アイコン (
) をクリックしてGradleの変更をロードします。
src/test/kotlinに移動し、ServerTest.ktファイルを開きます。
生成されたテストクラスを以下の実装に置き換えます:
kotlinこのセットアップで、以下のことを行いました:
- サービスがテスト環境内で実行されるように構成し、JSONシリアライズやWebSocketなど、本番環境と同じ機能を有効にしました。
- Ktor Client内でコンテントネゴシエーションとWebSocketサポートを構成しました。これがないと、クライアントはWebSocket接続を使用する際にオブジェクトをJSONとして(デ)シリアライズする方法を認識できません。Ktorクライアントの作成と構成方法を学びます。
- サービスから返されることが期待される
Tasksのリストを宣言しました。 clientオブジェクトの.webSocket関数を使用して、/tasksにリクエストを送信しました。- 着信タスクを
Flowとして受け取り、それらをリストに順次追加しました。 - すべてのタスクを受信したら、通常の方法で
expectedTasksとactualTasksを比較しました。
次のステップ
お疲れ様でした!WebSocket通信とKtor Clientによる自動テストを組み込むことで、タスクマネージャーサービスを大幅に強化できました。
