こんにちは。KOUKIです。
Go言語とJavaScriptでチャットアプリの作成方法を記事にしています。
前回は、Go言語側ではWebSocketsのエンドポイントを、JavaScript側ではWebSocketsオブジェクトをそれぞれ作成し、コネクションの確立をしました。
今回は、チャットに参加したユーザーのリストをページに表示する機能を作成します。
<目次>
前回
ユーザーのリストアップ
YOUR NAMEの横にあるボックスには、チャットに参加したユーザーの一覧を表示します。
USER NAMEのボックスに名前を入れた時、サーバー側へユーザー名を送信し、オンライン中の全てのユーザーに新しいユーザーが参加したことを伝えます。

ユーザー名をWebSocketsで送信する
WebSockets経由で、ユーザー名をサーバー側へ送信します。
scripts.jsに以下の処理を追加しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
document.addEventListener("DOMContentLoaded", function(){ // WebScoektオブジェクトの作成 // 接続を確立 // 接続がCLOSEDに変わった時に呼ばれる // エラーが発生した時に呼び出される // サーバーからメッセージが届いたときに呼び出される let userInput = document.getElementById("username") userInput.addEventListener("change", function() { let jsonData = {} // Action Nameをusernameにする jsonData["action"] = "username" jsonData["username"] = this.value; // user名を送信 socket.send(JSON.stringify(jsonData)) }) }) |
actionやusernameは、サーバー側で定義した以下の構造体に合わせています。
1 2 3 4 5 6 7 8 |
// connect.go // WebSockets送信データを格納 type WsPayload struct { Action string `json:"action"` Message string `json:"message"` Username string `json:"username"` Conn WebScoketConnection `json:"-"` } |
usernameアクションをハンドリング
ブラウザからusernameアクションを受け取れるようになったので、handlers.goに以下の機能を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// handlers.go package handlers import ( "chat-websockets/domain" "fmt" "log" "net/http" "sort" "github.com/CloudyKit/jet/v6" "github.com/gorilla/websocket" ) ... func ListenToWsChannel() { var response domain.WsJsonResponse for { // メッセージが入るまで、ここでブロック e := <-wsChan switch e.Action { case "username": // ここで、コネクションのユーザー名を格納 clients[e.Conn] = e.Username users := getUserList() response.Action = "list_users" response.ConnectedUsers = users // 後ほど構造体に追加 broadcastToAll(response) } } } func getUserList() []string { var clientList []string for _, client := range clients { if client != "" { clientList = append(clientList, client) } } sort.Strings(clientList) return clientList } |
ListenToWsChannel関数は、前回作成した関数です。
switch文の「username」ケースは、先ほどJavaScriptで設定したusername Actionに紐づいています。
新しく追加したgetUserList関数では、clients変数に格納したユーザー情報を全て取得し、ブロードキャストします。初回コネクション時(ページを開いた時)は、ユーザー名が空の状態で渡される為、その場合は処理をスキップします。
レスポンスにユーザーリストを含める
レスポンス構造体にユーザーリスト情報を持たせましょう。
1 2 3 4 5 6 7 8 9 10 11 |
// connect.go package domain import "github.com/gorilla/websocket" // WebSocketsからの返却用データの構造体 type WsJsonResponse struct { Action string `json:"action"` Message string `json:"message"` ConnectedUsers []string `json:"connected_users"` // 追加 } |
ユーザーリストの表示
scripts.jsのonmessageメソッドを以下のように書き換えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// scripts.js socket.onmessage = msg => { let data = JSON.parse(msg.data) console.log({data}) console.log("Action is", data.action) switch (data.action) { case "list_users": let ul = document.getElementById("online-users") while (ul.firstChild) ul.removeChild(ul.firstChild) if (data.connected_users.length > 0) { data.connected_users.forEach(function(item){ let li = document.createElement("li") li.appendChild(document.createTextNode(item)) ul.appendChild(li) }) } break } } |
先ほど、サーバー側でアクション(response.Action = “list_users”)を定義しました。そのため、switch文でlist_usersケースを定義しています。
そして、HTMLのul要素にユーザーリストを追加するため、li要素をcreateElementメソッドで作成しています。
createElementメソッドなどの使い方は、アプリケーション実装記事でよく使っているので、よかったら参考にしてください。
動作確認
アプリを再起動し、ブラウザから「http://localhost:8080/」にアクセスして、動作確認しましょう。

Self Note(ユーザー名)を打ち込んだらリストに表示されたので、OKですね。
バグの修正
一見、問題なく動作しているように見えますが、バグがあります。
下記の画像は、ページを開いて「firstUser」と打ち込んだあと、ページをリロードして、再度「firstUser」を打ち込んだ時のものです。

見ての通り、同一ページにも関わらず「firstUser」が重複して表示されています!
このバグを修正します。
原因
サーバー側の出力をよく見たら「failed websocket connection」と表示されていますね。
1 |
2021/04/05 06:39:49 Error repeated read on failed websocket connection |
ページをリロードした時に、別のWebSocketsコネクションを貼りにいくため、上記のエラーが表示されます。複数のコネクションをサーバー側で保持しているわけですね。
対策
ページをリロードした時に、ユーザーをleave(退室状態)にすることで、この事象を回避します。
ページ離脱時にleftアクションを設定
scripts.jsに、画面離脱時に「left」アクションを実行する処理を書きます。
1 2 3 4 5 6 7 8 9 |
// scripts.js // ページから離脱時に発生 window.onbeforeunload = function() { console.log("User Leaving") let jsonData = {} jsonData["action"] = "left" socket.send(JSON.stringify(jsonData)) } |
これで、ユーザーが退室したことをサーバーに知らせることができます。
leftアクションのハンドリング
次に、handlers.goのListenToWsChannel関数にleftアクションをハンドリングする処理を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// handlers.go ... func ListenToWsChannel() { var response domain.WsJsonResponse for { // メッセージが入るまで、ここでブロック e := <-wsChan switch e.Action { case "username": ... case "left": response.Action = "list_users" // clientsからユーザーを削除 delete(clients, e.Conn) users := getUserList() response.ConnectedUsers = users broadcastToAll(response) } } } |
wsChanからユーザーのコネクション情報が渡されるので、組み込み関数のdeleteでmapから削除すればOKです。
サーバーを立ち上げ直し、ブラウザのキャッシュクリア(念の為)をしてから、再度試してみてください。
次回
次回は、メッセージの送受信機能を実装していきます。
それでは、また!
コメントを残す
コメントを投稿するにはログインしてください。