Skip to content

Ktor を使用して Kotlin で WebSocket アプリケーションを作成する

コード例: tutorial-server-websockets

使用プラグイン:

Routing
Routingは、サーバーアプリケーションで受信リクエストを処理するためのコアプラグインです。
Static Content
スタイルシート、スクリプト、画像などの静的コンテンツを提供する方法を学びます。
Content Negotiation
ContentNegotiationプラグインは、クライアントとサーバー間のメディアタイプのネゴシエーションと、特定のフォーマットでのコンテンツのシリアライズ/デシリアライズという2つの主要な目的を果たします。
Ktor Server の WebSockets
WebSocketsプラグインを使用すると、サーバーとクライアント間で多方向通信セッションを作成できます。
kotlinx.serialization

この記事では、Ktor を使用して Kotlin で WebSocket アプリケーションを作成するプロセスについて説明します。

RESTful API の作成
Kotlin と Ktor を使用してバックエンドサービスを構築する方法を学び、JSON ファイルを生成する RESTful API の例を紹介します。
チュートリアルで扱った内容を基にしています。

この記事では、次のことを学習します:

  • JSON シリアライゼーションを使用するサービスを作成する。
  • WebSocket 接続を介してコンテンツを送受信する。
  • 複数のクライアントに同時にコンテンツをブロードキャストする。

前提条件

このチュートリアルは単独で実行できますが、

RESTful API の作成
Kotlin と Ktor を使用してバックエンドサービスを構築する方法を学び、JSON ファイルを生成する RESTful API の例を紹介します。
チュートリアルを完了して、
Content Negotiation
ContentNegotiationプラグインは、クライアントとサーバー間のメディアタイプのネゴシエーションと、特定のフォーマットでのコンテンツのシリアライズ/デシリアライズという2つの主要な目的を果たします。
と REST に慣れることをお勧めします。

IntelliJ IDEA をインストールすることをお勧めしますが、任意の他の IDE を使用することもできます。

Hello WebSockets

このチュートリアルでは、

RESTful API の作成
Kotlin と Ktor を使用してバックエンドサービスを構築する方法を学び、JSON ファイルを生成する RESTful API の例を紹介します。
チュートリアルで開発したタスクマネージャーサービスを基に、WebSocket 接続を介してクライアントと Task オブジェクトを交換する機能を追加します。これを実現するには、
WebSockets プラグイン
WebSocketsプラグインを使用すると、サーバーとクライアント間で多方向通信セッションを作成できます。
を追加する必要があります。既存のプロジェクトに手動で追加することもできますが、このチュートリアルのために、新しいプロジェクトを作成して最初から始めます。

プラグインを使用して初期プロジェクトを作成する

  1. Ktor Project Generator に移動します。

  2. Project artifact フィールドに、プロジェクトアーティファクトの名前として com.example.ktor-websockets-task-app を入力します。 Ktor Project Generatorでプロジェクトアーティファクトに名前を付ける

  3. プラグインセクションで、Add ボタンをクリックして以下のプラグインを検索し、追加します:

    1. Routing
    2. Content Negotiation
    3. Kotlinx.serialization
    4. WebSockets
    5. Static Content

    Ktor Project Generatorでプラグインを追加する

  4. プラグインを追加したら、プラグインセクションの右上にある 5 plugins ボタンをクリックして、追加されたプラグインを表示します。

    プロジェクトに追加されるすべてのプラグインのリストが表示されます: Ktor Project Generatorのプラグインリスト

  5. Download ボタンをクリックして、Ktor プロジェクトを生成し、ダウンロードします。

スターターコードを追加する

ダウンロードが完了したら、IntelliJ IDEA でプロジェクトを開き、以下の手順に従います:

  1. src/main/kotlin に移動し、model という新しいサブパッケージを作成します。
  2. model パッケージ内に、新しい Task.kt ファイルを作成します。

  3. Task.kt ファイルを開き、優先度を表す enum とタスクを表す data class を追加します:

    kotlin

    Task クラスには kotlinx.serialization ライブラリの Serializable 型アノテーションが付いていることに注意してください。これは、インスタンスを JSON に変換してネットワーク経由でその内容を転送できることを意味します。

    WebSockets プラグインを含めたため、 Sockets.kt ファイルが src/main/kotlin/com/example/plugins 内に生成されています。

  4. Sockets.kt ファイルを開き、既存の Application.configureSockets() 関数を以下の実装に置き換えます:

    kotlin

    このコードでは、以下の手順が実行されます:

    1. WebSockets プラグインがインストールされ、標準設定で構成されます。
    2. contentConverter プロパティが設定され、kotlinx.serialization ライブラリを介して送受信されるオブジェクトをプラグインがシリアライズできるようになります。
    3. ルーティングが単一のエンドポイントで構成され、相対 URL は /tasks です。
    4. リクエストを受信すると、タスクのリストが WebSocket 接続を介してシリアライズされます。
    5. すべてのアイテムが送信されると、サーバーは接続を閉じます。

    デモンストレーションのため、タスクの送信間に1秒の遅延が導入されています。これにより、クライアントでタスクが段階的に表示されるのを観察できます。この遅延がないと、この例は以前の記事で開発された

    RESTful サービス
    Kotlin と Ktor を使用してバックエンドサービスを構築する方法を学び、JSON ファイルを生成する RESTful API の例を紹介します。
    Web アプリケーション
    Ktor と Thymeleaf テンプレートを使用して Kotlin で Webサイトを構築する方法を学びます。
    とまったく同じように見えます。

    このイテレーションの最終ステップは、このエンドポイントのクライアントを作成することです。

    Static Content
    スタイルシート、スクリプト、画像などの静的コンテンツを提供する方法を学びます。
    プラグインを含めたため、 index.html ファイルが src/main/resources/static 内に生成されています。

  5. index.html ファイルを開き、既存のコンテンツを以下に置き換えます:

    html

    このページでは、すべての最新ブラウザで利用可能な WebSocket 型を使用しています。JavaScript でこのオブジェクトを作成し、エンドポイントの URL をコンストラクタに渡します。その後、onopenonclose、 および onmessage イベントのイベントハンドラーをアタッチします。onmessage イベントがトリガーされると、ドキュメントオブジェクトのメソッドを使用してテーブルに行を追加します。

  6. IntelliJ IDEA で、実行ボタン (IntelliJ IDEA 実行アイコン) をクリックしてアプリケーションを起動します。

  7. http://0.0.0.0:8080/static/index.html に移動します。ボタンと空のテーブルを含むフォームが表示されるはずです:

    ボタン1つがあるHTMLフォームを表示するウェブブラウザページ

    フォームをクリックすると、タスクがサーバーからロードされ、1秒に1つのペースで表示されます。その結果、テーブルには段階的にデータが入力されます。ブラウザの developer toolsJavaScript Console を開くと、ログに記録されたメッセージも表示できます。

    ボタンをクリックするとリストアイテムを表示するウェブブラウザページ

    これにより、サービスが期待どおりに動作していることがわかります。WebSocket 接続が開かれ、アイテムがクライアントに送信され、接続が閉じられます。基盤となるネットワークには多くの複雑さがありますが、Ktor はこれらすべてをデフォルトで処理します。

WebSockets を理解する

次のイテレーションに進む前に、WebSockets の基本をいくつか確認しておくと役立つかもしれません。 すでに WebSockets に精通している場合は、サービスの設計を改善するに進むことができます。

以前のチュートリアルでは、クライアントは HTTP リクエストを送信し、HTTP レスポンスを受信していました。これはうまく機能し、インターネットをスケーラブルで弾力性のあるものにしています。

しかし、次のようなシナリオには適していません:

  • コンテンツが時間とともに段階的に生成される場合。
  • イベントに応答してコンテンツが頻繁に変化する場合。
  • コンテンツが生成される際にクライアントがサーバーと対話する必要がある場合。
  • あるクライアントが送信したデータが、他のクライアントに迅速に伝播される必要がある場合。

これらのシナリオの例としては、株式取引、映画やコンサートのチケット購入、オンラインオークションでの入札、ソーシャルメディアのチャット機能などがあります。WebSockets は、これらの状況を処理するために開発されました。

WebSocket 接続は TCP 上で確立され、長期間持続することができます。この接続は「全二重通信」を提供し、クライアントがサーバーにメッセージを送信し、同時にサーバーからメッセージを受信できることを意味します。

WebSocket API は、4つのイベント (open、message、close、および error) と2つのアクション (send と close) を定義しています。 この機能へのアクセス方法は、異なる言語やライブラリによって異なる場合があります。 たとえば、Kotlin では、受信メッセージのシーケンスを Flow として消費できます。

設計を改善する

次に、より高度な例のためのスペースを確保するために、既存のコードをリファクタリングします。

  1. model パッケージに、新しい TaskRepository.kt ファイルを作成します。

  2. TaskRepository.kt を開き、TaskRepository 型を追加します:

    kotlin

    このコードは以前のチュートリアルで覚えているかもしれません。

  3. plugins パッケージに移動し、Sockets.kt ファイルを開きます。
  4. これで、TaskRepository を利用して Application.configureSockets() のルーティングを簡素化できます:

    kotlin

WebSockets を介してメッセージを送信する

WebSockets の威力を示すために、次の新しいエンドポイントを作成します:

  • クライアントが起動すると、すべての既存のタスクを受信します。
  • クライアントはタスクを作成して送信できます。
  • あるクライアントがタスクを送信すると、他のクライアントに通知されます。
  1. Sockets.kt ファイルで、現在の configureSockets() メソッドを以下の実装に置き換えます:

    kotlin

    このコードで、次のことを行いました:

    • すべての既存のタスクを送信する機能をヘルパーメソッドにリファクタリングしました。
    • routing セクションで、すべてのクライアントを追跡するための session オブジェクトのスレッドセーフなリストを作成しました。
    • 相対 URL が /task2 の新しいエンドポイントを追加しました。クライアントがこのエンドポイントに接続すると、対応する session オブジェクトがリストに追加されます。その後、サーバーは新しいタスクを受信するのを待つ無限ループに入ります。新しいタスクを受信すると、サーバーはそれをリポジトリに保存し、現在のクライアントを含むすべてのクライアントにコピーを送信します。

    この機能をテストするために、index.html の機能を拡張する新しいページを作成します。

  2. src/main/resources/static 内に、wsClient.html という新しい HTML ファイルを作成します。

  3. wsClient.html を開き、以下の内容を追加します:

    html

    この新しいページでは、ユーザーが新しいタスクの情報を入力できる HTML フォームを導入しています。 フォームを送信すると、sendTaskToServer イベントハンドラーが呼び出されます。これは、フォームデータを持つ JavaScript オブジェクトを構築し、WebSocket オブジェクトの send メソッドを使用してサーバーに送信します。

  4. IntelliJ IDEA で、再実行ボタン (IntelliJ IDEA 再実行アイコン) をクリックしてアプリケーションを再起動します。

  5. この機能をテストするには、2つのブラウザを並べて開き、以下の手順に従います。

    1. ブラウザ A で、 http://0.0.0.0:8080/static/wsClient.html に移動します。デフォルトのタスクが表示されるはずです。
    2. ブラウザ A で新しいタスクを追加します。新しいタスクはそのページのテーブルに表示されるはずです。
    3. ブラウザ B で、 http://0.0.0.0:8080/static/wsClient.html に移動します。デフォルトのタスクと、ブラウザ A で追加した新しいタスクが表示されるはずです。
    4. いずれかのブラウザでタスクを追加します。新しいアイテムが両方のページに表示されるはずです。
    HTMLフォームを介して新しいタスクを作成する様子を示す2つのウェブブラウザページが並んでいる

自動テストを追加する

QA プロセスを効率化し、高速で再現性があり、ハンズフリーにするために、Ktor の組み込みの

自動テストサポート
特別なテストエンジンを使用してサーバーアプリケーションをテストする方法を学びます。
を使用できます。以下の手順に従ってください:

  1. Ktor クライアント内で

    Content Negotiation
    ContentNegotiationプラグインは、クライアントとサーバー間のメディアタイプのネゴシエーションと、特定のフォーマットでのコンテンツのシリアライズ/デシリアライズという2つの主要な目的を果たします。
    のサポートを設定できるように、 build.gradle.kts に以下の依存関係を追加します:

    kotlin
  2. IntelliJ IDEA で、エディタの右側にある通知 Gradle アイコン (IntelliJ IDEA Gradle アイコン) をクリックして、Gradle の変更をロードします。

  3. src/test/kotlin/com/example に移動し、 ApplicationTest.kt ファイルを開きます。

  4. 生成されたテストクラスを以下の実装に置き換えます:

    kotlin

    このセットアップで、次のことを行います:

    • サービスをテスト環境で実行するように構成し、ルーティング、JSON シリアライゼーション、WebSockets など、本番環境と同じ機能を有効にします。
    • Ktor クライアント
      Ktor クライアントを作成および構成する方法を学びます。
      内で Content Negotiation および WebSocket サポートを構成します。これがないと、クライアントは WebSocket 接続を使用する際にオブジェクトを JSON として (デ)シリアライズする方法を認識しません。
    • サービスが返すことを期待する Tasks のリストを宣言します。
    • クライアントオブジェクトの websocket メソッドを使用して、/tasks へのリクエストを送信します。
    • 受信するタスクを flow として消費し、段階的にリストに追加します。
    • すべてのタスクが受信されたら、通常の方法で expectedTasksactualTasks を比較します。

次のステップ

素晴らしい!Ktor クライアントとの WebSocket 通信と自動テストを組み込むことで、タスクマネージャーサービスが大幅に強化されました。

次のチュートリアル
Exposed SQL ライブラリを使用して Ktor サービスをデータベースリポジトリに接続するプロセスを学びます。
に進んで、Exposed ライブラリを使用してサービスがリレーショナルデータベースとシームレスに連携する方法を探ります。