Ktor を使用して Kotlin で WebSocket アプリケーションを作成する
コード例: tutorial-server-websockets
使用プラグイン:
この記事では、Ktor を使用して Kotlin で WebSocket アプリケーションを作成するプロセスについて説明します。
この記事では、次のことを学習します:
- JSON シリアライゼーションを使用するサービスを作成する。
- WebSocket 接続を介してコンテンツを送受信する。
- 複数のクライアントに同時にコンテンツをブロードキャストする。
前提条件
このチュートリアルは単独で実行できますが、
IntelliJ IDEA をインストールすることをお勧めしますが、任意の他の IDE を使用することもできます。
Hello WebSockets
このチュートリアルでは、
Task
オブジェクトを交換する機能を追加します。これを実現するには、プラグインを使用して初期プロジェクトを作成する
Ktor Project Generator に移動します。
Project artifact フィールドに、プロジェクトアーティファクトの名前として com.example.ktor-websockets-task-app を入力します。
プラグインセクションで、Add ボタンをクリックして以下のプラグインを検索し、追加します:
- Routing
- Content Negotiation
- Kotlinx.serialization
- WebSockets
- Static Content
プラグインを追加したら、プラグインセクションの右上にある 5 plugins ボタンをクリックして、追加されたプラグインを表示します。
プロジェクトに追加されるすべてのプラグインのリストが表示されます:
Download ボタンをクリックして、Ktor プロジェクトを生成し、ダウンロードします。
スターターコードを追加する
ダウンロードが完了したら、IntelliJ IDEA でプロジェクトを開き、以下の手順に従います:
- src/main/kotlin に移動し、model という新しいサブパッケージを作成します。
model パッケージ内に、新しい Task.kt ファイルを作成します。
Task.kt ファイルを開き、優先度を表す
enum
とタスクを表すdata class
を追加します:kotlinTask
クラスにはkotlinx.serialization
ライブラリのSerializable
型アノテーションが付いていることに注意してください。これは、インスタンスを JSON に変換してネットワーク経由でその内容を転送できることを意味します。WebSockets プラグインを含めたため、 Sockets.kt ファイルが src/main/kotlin/com/example/plugins 内に生成されています。
Sockets.kt ファイルを開き、既存の
Application.configureSockets()
関数を以下の実装に置き換えます:kotlinこのコードでは、以下の手順が実行されます:
- WebSockets プラグインがインストールされ、標準設定で構成されます。
contentConverter
プロパティが設定され、kotlinx.serialization ライブラリを介して送受信されるオブジェクトをプラグインがシリアライズできるようになります。- ルーティングが単一のエンドポイントで構成され、相対 URL は
/tasks
です。 - リクエストを受信すると、タスクのリストが WebSocket 接続を介してシリアライズされます。
- すべてのアイテムが送信されると、サーバーは接続を閉じます。
デモンストレーションのため、タスクの送信間に1秒の遅延が導入されています。これにより、クライアントでタスクが段階的に表示されるのを観察できます。この遅延がないと、この例は以前の記事で開発された
RESTful サービスやKotlin と Ktor を使用してバックエンドサービスを構築する方法を学び、JSON ファイルを生成する RESTful API の例を紹介します。Web アプリケーションとまったく同じように見えます。Ktor と Thymeleaf テンプレートを使用して Kotlin で Webサイトを構築する方法を学びます。このイテレーションの最終ステップは、このエンドポイントのクライアントを作成することです。
Static Contentプラグインを含めたため、 index.html ファイルが src/main/resources/static 内に生成されています。スタイルシート、スクリプト、画像などの静的コンテンツを提供する方法を学びます。index.html ファイルを開き、既存のコンテンツを以下に置き換えます:
htmlこのページでは、すべての最新ブラウザで利用可能な WebSocket 型を使用しています。JavaScript でこのオブジェクトを作成し、エンドポイントの URL をコンストラクタに渡します。その後、
onopen
、onclose
、 およびonmessage
イベントのイベントハンドラーをアタッチします。onmessage
イベントがトリガーされると、ドキュメントオブジェクトのメソッドを使用してテーブルに行を追加します。IntelliJ IDEA で、実行ボタン (
) をクリックしてアプリケーションを起動します。
http://0.0.0.0:8080/static/index.html に移動します。ボタンと空のテーブルを含むフォームが表示されるはずです:
フォームをクリックすると、タスクがサーバーからロードされ、1秒に1つのペースで表示されます。その結果、テーブルには段階的にデータが入力されます。ブラウザの developer tools で JavaScript Console を開くと、ログに記録されたメッセージも表示できます。
これにより、サービスが期待どおりに動作していることがわかります。WebSocket 接続が開かれ、アイテムがクライアントに送信され、接続が閉じられます。基盤となるネットワークには多くの複雑さがありますが、Ktor はこれらすべてをデフォルトで処理します。
WebSockets を理解する
次のイテレーションに進む前に、WebSockets の基本をいくつか確認しておくと役立つかもしれません。 すでに WebSockets に精通している場合は、サービスの設計を改善するに進むことができます。
以前のチュートリアルでは、クライアントは HTTP リクエストを送信し、HTTP レスポンスを受信していました。これはうまく機能し、インターネットをスケーラブルで弾力性のあるものにしています。
しかし、次のようなシナリオには適していません:
- コンテンツが時間とともに段階的に生成される場合。
- イベントに応答してコンテンツが頻繁に変化する場合。
- コンテンツが生成される際にクライアントがサーバーと対話する必要がある場合。
- あるクライアントが送信したデータが、他のクライアントに迅速に伝播される必要がある場合。
これらのシナリオの例としては、株式取引、映画やコンサートのチケット購入、オンラインオークションでの入札、ソーシャルメディアのチャット機能などがあります。WebSockets は、これらの状況を処理するために開発されました。
WebSocket 接続は TCP 上で確立され、長期間持続することができます。この接続は「全二重通信」を提供し、クライアントがサーバーにメッセージを送信し、同時にサーバーからメッセージを受信できることを意味します。
WebSocket API は、4つのイベント (open、message、close、および error) と2つのアクション (send と close) を定義しています。 この機能へのアクセス方法は、異なる言語やライブラリによって異なる場合があります。 たとえば、Kotlin では、受信メッセージのシーケンスを Flow として消費できます。
設計を改善する
次に、より高度な例のためのスペースを確保するために、既存のコードをリファクタリングします。
model パッケージに、新しい TaskRepository.kt ファイルを作成します。
TaskRepository.kt を開き、
TaskRepository
型を追加します:kotlinこのコードは以前のチュートリアルで覚えているかもしれません。
- plugins パッケージに移動し、Sockets.kt ファイルを開きます。
これで、
TaskRepository
を利用してApplication.configureSockets()
のルーティングを簡素化できます:kotlin
WebSockets を介してメッセージを送信する
WebSockets の威力を示すために、次の新しいエンドポイントを作成します:
- クライアントが起動すると、すべての既存のタスクを受信します。
- クライアントはタスクを作成して送信できます。
- あるクライアントがタスクを送信すると、他のクライアントに通知されます。
Sockets.kt ファイルで、現在の
configureSockets()
メソッドを以下の実装に置き換えます:kotlinこのコードで、次のことを行いました:
- すべての既存のタスクを送信する機能をヘルパーメソッドにリファクタリングしました。
routing
セクションで、すべてのクライアントを追跡するためのsession
オブジェクトのスレッドセーフなリストを作成しました。- 相対 URL が
/task2
の新しいエンドポイントを追加しました。クライアントがこのエンドポイントに接続すると、対応する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 クライアント内で
Content Negotiationのサポートを設定できるように、 build.gradle.kts に以下の依存関係を追加します:ContentNegotiationプラグインは、クライアントとサーバー間のメディアタイプのネゴシエーションと、特定のフォーマットでのコンテンツのシリアライズ/デシリアライズという2つの主要な目的を果たします。kotlinIntelliJ IDEA で、エディタの右側にある通知 Gradle アイコン (
) をクリックして、Gradle の変更をロードします。
src/test/kotlin/com/example に移動し、 ApplicationTest.kt ファイルを開きます。
生成されたテストクラスを以下の実装に置き換えます:
kotlinこのセットアップで、次のことを行います:
- サービスをテスト環境で実行するように構成し、ルーティング、JSON シリアライゼーション、WebSockets など、本番環境と同じ機能を有効にします。
- Ktor クライアント内で Content Negotiation および WebSocket サポートを構成します。これがないと、クライアントは WebSocket 接続を使用する際にオブジェクトを JSON として (デ)シリアライズする方法を認識しません。Ktor クライアントを作成および構成する方法を学びます。
- サービスが返すことを期待する
Tasks
のリストを宣言します。 - クライアントオブジェクトの
websocket
メソッドを使用して、/tasks
へのリクエストを送信します。 - 受信するタスクを
flow
として消費し、段階的にリストに追加します。 - すべてのタスクが受信されたら、通常の方法で
expectedTasks
とactualTasks
を比較します。
次のステップ
素晴らしい!Ktor クライアントとの WebSocket 通信と自動テストを組み込むことで、タスクマネージャーサービスが大幅に強化されました。