Skip to content

WebSocketアプリケーションの作成

KotlinとKtorでWebSocketアプリケーションを作成する

コード例: tutorial-server-websockets

使用するプラグイン:

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

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

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

この記事では、以下の方法について説明します:

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

前提条件

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

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

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. Content Negotiation
    2. kotlinx.serialization
    3. WebSockets
    4. Static Content

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

  4. プラグインを追加すると、プラグインセクションの右上に表示されます。

    プロジェクトに追加されるすべてのプラグインのリストが表示されます: 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プラグインを含めたため、ジェネレーターによってsrc/main/kotlin内のWebsockets.ktファイルと、Routing.ktファイルにwebSocketルートが追加されています。

  4. Websockets.ktファイルを開き、既存の.configureWebsockets()関数を次のように置き換えます:
    kotlin
    • WebSocketsプラグインがインストールされ、標準設定で構成されます。
    • contentConverterプロパティが設定され、プラグインがkotlinx.serializationライブラリを通じて送受信されるオブジェクトをシリアライズできるようになります。
  5. 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ファイルが追加されています。

  6. index.htmlファイルを開き、既存の内容を以下のように置き換えます:

    html

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

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

  8. http://0.0.0.0:8080/static/index.htmlにアクセスします。ボタンのあるフォームと空のテーブルが表示されるはずです:

    ボタン1つのHTMLフォームを表示しているWebブラウザページ

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

    ボタンクリックでリストアイテムを表示しているWebブラウザページ

    これで、サービスは期待どおりに動作しています。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として利用できます。

設計の改善

次に、より高度な例に対応できるように既存のコードをリファクタリングします。

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

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

    kotlin

    このコードは、以前のチュートリアルで見た覚えがあるかもしれません。

  3. src/main/kotlinに移動し、Routing.ktファイルを開きます。
  4. TaskRepositoryを利用することで、Application.configureRouting()のルーティングを簡素化できます:

    kotlin

WebSocket経由でメッセージを送信する

WebSocketのパワーを説明するために、次のような新しいエンドポイントを作成します:

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

    kotlin

    このコードで以下のことを行いました:

    • 既存のすべてのタスクを送信する機能をヘルパーメソッドにリファクタリングしました。
    • routing {}ブロック内で、すべてのクライアントを追跡するためのスレッドセーフなsessionオブジェクトのリストを作成しました。
    • 相対URLが/tasks2の新しいエンドポイントを追加しました。クライアントがこのエンドポイントに接続すると、対応する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. どちらかのブラウザでタスクを追加します。両方のページに新しいアイテムが表示されるはずです。
    2つのWebブラウザページを並べてHTMLフォームから新しいタスクを作成するデモンストレーション

自動テストの追加

QAプロセスを効率化し、高速、再現可能、かつハンズフリーにするために、Ktorに組み込まれている

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

  1. Ktor Client内で

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

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

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

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

    kotlin

    このセットアップで、以下のことを行いました:

    • サービスがテスト環境内で実行されるように構成し、JSONシリアライズやWebSocketなど、本番環境と同じ機能を有効にしました。
    • Ktor Client
      Ktorクライアントの作成と構成方法を学びます。
      内でコンテントネゴシエーションとWebSocketサポートを構成しました。これがないと、クライアントはWebSocket接続を使用する際にオブジェクトをJSONとして(デ)シリアライズする方法を認識できません。
    • サービスから返されることが期待されるTasksのリストを宣言しました。
    • clientオブジェクトの.webSocket関数を使用して、/tasksにリクエストを送信しました。
    • 着信タスクをFlowとして受け取り、それらをリストに順次追加しました。
    • すべてのタスクを受信したら、通常の方法でexpectedTasksactualTasksを比較しました。

次のステップ

お疲れ様でした!WebSocket通信とKtor Clientによる自動テストを組み込むことで、タスクマネージャーサービスを大幅に強化できました。

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