Melody - Golang learning step 7-1

  • 公開日
  • カテゴリ:RealtimeCommunication
  • タグ:Golang,roadmap.sh,学習メモ
Melody - Golang learning step 7-1

roadmap.sh > Go > Realtime Communication > Melody の学習を進めていきます。

※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。

contents

  1. 開発環境
  2. 参考 URL
  3. Melody
  4. Melody のインストール手順
    1. 1. Go モジュールの初期化
    2. 2. Melody パッケージのインストール
    3. 3. ミニマムの実装で動作確認
  5. WebSocket サーバーを作ってチャットアプリを動かそう
    1. 1. Websocket サーバー構築
    2. 2. クライアント画面を作成
    3. 3. 動作確認
  6. 誰が送信したのかをわかるようにしてみる
    1. 1. Websocket サーバー改修
    2. 2. クライアント画面改修
    3. 3. 動作確認

開発環境

  • チップ: Apple M2 Pro
  • OS: macOS Sonoma
  • go version: go1.23.2 darwin/arm64

参考 URL

Melody

Melody は、WebSocket を簡単かつ効率的に扱うための Go パッケージです。WebSocket サーバーの実装において必要な接続管理やメッセージのブロードキャストなどの基本機能を提供し、シンプルな API で実装できます。

Melody のインストール手順

1. Go モジュールの初期化

プロジェクトを作成し初期化します。

go mod init melody-example

2. Melody パッケージのインストール

以下のコマンドを実行して Melody をインストールします。

go get -u github.com/olahol/melody

コマンドを実行すると、go.mod ファイルに Melody が依存関係として追加されます。

require (
  github.com/gorilla/websocket v1.5.3 // indirect
  github.com/olahol/melody v1.2.1 // indirect
)

3. ミニマムの実装で動作確認

main.go を作成し、以下のようにコードを書き、melody.New() を使用して Melody インスタンスを作成できるか確認します。

package main

import (
    "fmt"
    "github.com/olahol/melody"
)

func main() {
    m := melody.New()
    fmt.Println("Melody インスタンスが作成されました:", m)
}

実行してエラーが出なければインストール成功です。

Melody インスタンスが作成されました: &{0xc00000c1e0}

WebSocket サーバーを作ってチャットアプリを動かそう

以下は、Melody を使った WebSocket サーバーの基本的な使い方です。簡単なリアルタイムチャットを実装し動作させます。

1. Websocket サーバー構築

main.go に以下、Melody を利用して WebSocket サーバーを構築します。

package main

import (
  "net/http"
  "github.com/olahol/melody"
)

func main() {
  // Melody のインスタンスを作成
  m := melody.New()

  // 静的な HTML を提供するハンドラー
  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, "index.html")
  })

  // WebSocket リクエストを処理するハンドラー
  http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
    err := m.HandleRequest(w, r)
    if err != nil {
      fmt.Printf("error handling request: %v\n", err)
    }
  })

  // メッセージ受信時の処理
  m.HandleMessage(func(s *melody.Session, msg []byte) {
    // 受信したメッセージをすべてのクライアントにブロードキャスト
    err := m.Broadcast(msg)
    if err != nil {
      fmt.Printf("broadcast error: %v\n", err)
    }
  })

  // サーバー起動
  err := http.ListenAndServe(":8080", nil)
  if err != nil {
    fmt.Printf("error: %v\n", err)
  }
}
  1. Melody インスタンスの作成
    • melody.New() を使って WebSocket サーバーを管理するインスタンスを作成します。
  2. HTTP ハンドラー
    • / エンドポイントで静的な HTML ファイルを提供します。
    • /ws エンドポイントで WebSocket 接続を処理します。
  3. メッセージ処理
    • m.HandleMessage を使い、クライアントからメッセージを受信したときの動作を定義します。
    • m.Broadcast によって受信したメッセージを全クライアントに送信します。

2. クライアント画面を作成

次に、チャットを行うクライアント画面を作成します。index.html を作成し、以下を記述します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Melody WebSocket Demo</title>
</head>
<body>
    <h1>Melody チャットデモ</h1>
    <input id="message" type="text" placeholder="メッセージを入力">
    <button onclick="sendMessage()">送信</button>
    <ul id="messages"></ul>

    <script>
        // WebSocket 接続を作成
        const ws = new WebSocket("ws://localhost:8080/ws");

        // メッセージ受信時の処理
        ws.onmessage = (event) => {
            const li = document.createElement("li");
            li.textContent = event.data;
            document.getElementById("messages").appendChild(li);
        };

        // メッセージ送信処理
        function sendMessage() {
            const input = document.getElementById("message");
            ws.send(input.value);
            input.value = ""; // 入力欄をクリア
        }
    </script>
</body>
</html>
  • クライアントは JavaScript を使い、サーバーと WebSocket 接続を確立します。
  • 接続後、サーバーから送信されたメッセージを受け取り、画面に表示します。

3. 動作確認

以下コマンドで Websocket サーバーを起動します。

go run main.go

ブラウザで http://localhost:8080 にアクセスすると、チャット画面が表示されます。

この時、開発者ツール(F12)を開き、"Network" タブで /ws エンドポイントが 101 Switching Protocols になっていれば、WebSocket 接続が成功していることを示しています。

例えば、2 つのタブやブラウザで同じ画面を開き、片方で入力、送信を行えば、もう一方にもメッセージが反映されます。

誰が送信したのかをわかるようにしてみる

ミニマムの実装だけで Websocket を利用したリアルタイムチャットが作成できました。

次は、誰が送信したのかをわかるようにしてみます。

1. Websocket サーバー改修

  1. セッションごとにユーザー情報を管理する
    • Melody のセッション (melody.Session) に送信者情報(ユーザー名など)を保存します。
  2. 送信メッセージに送信者情報を含める
    • 送信者の情報をメッセージデータと一緒に送信し、他のクライアントに表示します。

以下の例では、ユーザー名を接続時にクエリパラメータで指定し、その情報をメッセージに含めています。

package main

import (
  "encoding/json"
  "fmt"
  "github.com/olahol/melody"
  "net/http"
)

// メッセージの構造体
type Message struct {
  Sender  string `json:"sender"`
  Content string `json:"content"`
}

func main() {
  m := melody.New()

  // 1. クライアント接続時にユーザー名をセッションに保存
  m.HandleConnect(func(s *melody.Session) {
    query := s.Request.URL.Query()
    username := query.Get("username")
    if username == "" {
      username = "匿名ユーザー"
    }
    // 2. セッションにユーザー名を保存
    s.Set("username", username)
  })

  // メッセージ受信時の処理
  m.HandleMessage(func(s *melody.Session, msg []byte) {
    // セッションからユーザー名を取得
    username, exists := s.Get("username")
    if !exists {
      username = "匿名ユーザー"
    }

    // 3. 送信メッセージを構築(受信メッセージに送信者情報を追加)
    message := Message{
      Sender:  username.(string),
      Content: string(msg),
    }

    // メッセージを JSON に変換
    messageJSON, err := json.Marshal(message)
    if err != nil {
      return
    }

    // ブロードキャスト
    err = m.Broadcast(messageJSON)
    if err != nil {
      fmt.Printf("broadcast error: %v\n", err)
    }
  })

  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, "index.html")
  })

  http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
    err := m.HandleRequest(w, r)
    if err != nil {
      fmt.Printf("error handling request: %v\n", err)
    }
  })

  err := http.ListenAndServe(":8080", nil)
  if err != nil {
    fmt.Printf("error: %v\n", err)
  }
}
  1. クライアント接続時にユーザー名をセッションに保存
    • WebSocket 接続時に、クエリパラメータ (?username=<名前>) を利用してユーザー名を取得。
    • クエリパラメータが空の場合は、デフォルトで「匿名ユーザー」とする。
  2. セッションにユーザー名を保存
    • Melody のセッションにユーザー名を保存。
    • s.Set("username", username) を使用してセッションに紐付ける。
  3. 送信メッセージを構築(受信メッセージに送信者情報を追加)
    • クライアントからメッセージを受信した際に、セッションから送信者(ユーザー名)を取得。
    • メッセージ内容を構造体 (Message) にまとめ、JSON に変換してブロードキャスト。

2. クライアント画面改修

index.html も改修しましょう。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Melody チャット</title>
</head>
<body>
<h1>Melody チャット</h1>
<p id="username-display"></p> <!-- ユーザー名を表示する場所 -->
<input id="message" type="text" placeholder="メッセージを入力">
<button onclick="sendMessage()">送信</button>
<ul id="messages"></ul>

<script>
    // 1. ユーザー名の取得
    let username = prompt("ユーザー名を入力してください", "匿名ユーザー");
    if (!username) {
        username = "匿名ユーザー"; // 入力が空の場合のデフォルト値
    }

    // ユーザー名を画面に表示
    document.getElementById("username-display").textContent = `あなたの名前: ${username}`;

    // 2. WebSocket 接続
    const ws = new WebSocket(`ws://localhost:8080/ws?username=${encodeURIComponent(username)}`);

    // 3. メッセージ受信時の処理
    ws.onmessage = (event) => {
        const data = JSON.parse(event.data);
        const li = document.createElement("li");
        li.textContent = `${data.sender}: ${data.content}`;
        document.getElementById("messages").appendChild(li);
    };

    // メッセージ送信処理
    function sendMessage() {
        const input = document.getElementById("message");
        if (input.value.trim() !== "") {
            ws.send(input.value);
            input.value = ""; // 入力欄をクリア
        }
    }
</script>
</body>
</html>
  1. ユーザー名の取得と表示
    • prompt でユーザー名を取得し、画面に表示。
    • <p> 要素を追加してユーザー名を動的に表示する部分を追加。
  2. WebSocket 接続’ユーザー名を WebSocket 接続時に送信)
    • クライアントがサーバーに接続する際、クエリパラメータとしてユーザー名を渡す。
  3. メッセージ受信時の処理(メッセージの構造変更に対応)
    • サーバーから送られてくるメッセージが JSON 形式になったため、JSON.parse を使って受信データを解析。
    • 受信データから sender(送信者名)と content(メッセージ内容)を取得して表示。

3. 動作確認

先ほどと同様、Websocket サーバーを起動して、動作を確認します。

go run main.go

それぞれの画面が誰の画面で、それぞれの発言が誰の発言かがわかるようになりました。

実装量自体は多くないものの、簡単にリアルタイムチャットの機能が構築できてしまうのはすごいですね。

まとめ

  • Melody は、WebSocket サーバーを簡単に構築できる Go パッケージ
  • 接続管理やメッセージブロードキャストがシンプルに実装可能
  • 簡単なチャットアプリを通して基本的な使い方を学べるくらいに簡単
  • セッションやメッセージ形式を拡張しやすい柔軟性を持つ
  • 初学者にも扱いやすく、リアルタイムアプリケーションの導入に最適


[Next] Step 8-1: Heimdall

[Prev] Step 6-3: Zerolog

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級