こんにちは、 KOUKIです。
本記事では、掲示板アプリ開発のプロセスをハンズオン形式で記事にしています!
前回は、WebSocketsコネクションの確立処理を実装しました。
今回は、投稿機能の実装をしていきたいと思います。
<目次>
前回
まとめページ
投稿機能に必要なこと
投稿機能を実装するために、以下の作業が必要になります。
2. ユーザー退室機能の作成
3. 投稿機能の作成
4. コネクト機能の作成
ユーザーアクセス機能の作成
ユーザー名を入力したら、WebSocketsコネクションにユーザー名を紐付たいと思います。

usernameアクションの送信
scripts.jsに、以下の機能を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// scripts.js let socket = null; document.addEventListener('DOMContentLoaded', function(){ // WebSocketsオブジェクトの作成 // 接続を確立時 // 接続が切時時時 // エラー発時生時 // メッセージ受信時 let userInput = document.getElementById("username") userInput.addEventListener("change", function() { let jsonData = {} jsonData["action"] = "username" jsonData["username"] = this.value socket.send(JSON.stringify(jsonData)) }) }) |
これで、ユーザー名をサーバへ送信することができるようになりました。
jsonDataのパラメータは、connect.goに定義したpayloadに合わせています。
1 2 3 4 5 6 7 8 |
// connect.go // WebSockets受信データ type WsPayload struct { Action string `json:"action"` Post string `json:"post"` Username string `json:"username"` Conn WebSocketConnection `json:"-"` } |
sendメソッドは、Websocketsを介してデータをサーバー側へ送れます。
JSON.stringifyは、JavaScriptのオブジェクトをJSONデータにします。
1 2 3 4 5 6 7 8 9 |
let jsonData = {} jsonData["action"] = "username" jsonData["username"] = "selfnote" jsonData {action: "username", username: "selfnote"} JSON.stringify(jsonData) "{"action":"username","username":"selfnote"}" |
ユーザー名をWebSocketsコネクションに紐付ける
ユーザー名をブラウザから送信できるようになったので、次は、サーバー側でWebコネクションと紐づける処理を実装します。
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 |
func ListenToWsChannel() { // var response domain.WsJsonResponse for { // wsChanにデータが入るまでブロックするので、無限ループでOK payload := <-wsChan switch payload.Action { case "username": clients[payload.Conn] = payload.Username } // 後で、Action別の処理を実装する // response.Action = "Sample Action" // response.Post = fmt.Sprintf(` // <div class="post"> // <p>%s</p> // <small class="name">%s</small> // </div>`, payload.Post, payload.Username) // broadcastToAllUser(response) } } |
処理が途中なのでコメントアウトにしているところもありますが、今は無視してください。
大切なのは、payloadのActionをSwitch文で条件分岐している処理です。このようにActionを定義することで、実装が明確化して、わかりやすいコードになります。
今回は、「username」アクションを送信したので、その情報をclients変数のvalueに格納しました。これで、コネクション情報とユーザ名の紐付けが完了です。
ユーザー退室機能の作成
次は、ユーザーが退室した時の処理を実装します。
leftアクションの送信
sctipts.jsに次の処理を追加してください。
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)) } |
今回は、ページから離脱した時に、退室したことにしました。
WebSocketsコネクションを削除する
次に、handlers.goに「left」アクションのハンドリング処理を実装します。
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 |
// handlers.g func ListenToWsChannel() { // var response domain.WsJsonResponse for { // wsChanにデータが入るまでブロックするので、無限ループでOK payload := <-wsChan switch payload.Action { case "username": clients[payload.Conn] = payload.Username case "left": // clients変数からコネクションを削除 delete(clients, payload.Conn) } // 後で、Action別の処理を実装する // response.Action = "Sample Action" // response.Post = fmt.Sprintf(` // <div class="post"> // <p>%s</p> // <small class="name">%s</small> // </div>`, payload.Post, payload.Username) // broadcastToAllUser(response) } } |
switch文のcaseに「left」を追加し、clients変数から対象のコネクションを削除しました。
投稿機能の作成
いよいよ投稿機能の作成に入ります。
投稿データを送信する
まずは、掲示板から投稿データをサーバー側へ送信できるようにしましょう。
scrripts.jsに以下の機能を追加します。
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 |
// scripts.js ... document.addEventListener('DOMContentLoaded', function(){ // WebSocketsオブジェクトの作成 // 接続を確立時 // メッセージ受信時 ... document.getElementById("post").addEventListener("keydown", function(event){ if (event.code === "Enter") { if (!socket) { console.log("no connection") return } // イベントの伝搬やデフォルトの挙動をキャンセル event.preventDefault() event.stopPropagation() sendPost() } }) }) function sendPost() { console.log("Send Post...") let jsonData = {} jsonData["action"] = "broadcast" jsonData["username"] = document.getElementById("username").value jsonData["post"] = document.getElementById("post").value // サーバーへメッセージを送信 socket.send(JSON.stringify(jsonData)) // 入力項目を空にする document.getElementById("post").value = "" } |
この処理により、投稿ボックスにデータを入力後「Enter」を押下することで、WebSockets経由で投稿データを送信することができるようになりました。
尚、jsonDataのパラメータに指定している値は、connect.goに定義しているものに合わせています。
1 2 3 4 5 6 7 |
// WebSockets受信データ type WsPayload struct { Action string `json:"action"` Post string `json:"post"` Username string `json:"username"` Conn WebSocketConnection `json:"-"` } |
「POST」ボタンを押下した時も、投稿データを送信できるようにしましょう。
HTMLのbuttonタグに「onclick=”sendPost()”」を追加します。
1 2 3 4 5 6 |
<!-- home.jet --> <div class="form-area"> <input type="text" class="username" id="username" autocomplete="off" placeholder="Your Name"> <textarea name="post" id="post" cols="30" rows="10" autocomplete="off"></textarea> <button id="submit" class="submit" onclick="sendPost()">POST</button> </div> |
メッセージをブロードキャストする
次に、投稿データをブロードキャストしましょう。
handlers.goのListenToWsChannel関数に以下の修正を加えます。
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 |
func ListenToWsChannel() { var response domain.WsJsonResponse for { // wsChanにデータが入るまでブロックするので、無限ループでOK payload := <-wsChan switch payload.Action { case "username": clients[payload.Conn] = payload.Username case "left": // clients変数からコネクションを削除 delete(clients, payload.Conn) case "broadcast": response.Action = "broadcast" response.Post = fmt.Sprintf(` <div class="post"> <p>%s</p> <small class="name">%s</small> </div>`, payload.Post, payload.Username) broadcastToAllUser(response) } } } |
クライアントから「broadcast」アクションの投稿データを送信しているので、上記のようにswitch文のcaseにbroadcastを指定しています。
broadcastToAllUser関数は、掲示板にアクセスしているユーザー全てに投稿データを送信することができる関数です。※前回の記事を参照してください
掲示板にメッセージを表示する
最後に、掲示板にメッセージを表示する処理を実装しましょう。
scripts.jsのsocket.onmessageメソッドに以下の処理を追加します。
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(){ // WebSocketsオブジェクトの作成 // 接続を確立時 // メッセージ受信時 socket.onmessage = msg => { // JSONデータをJavaScriptオブジェクトへパース let postData = JSON.parse(msg.data) let postContainer = document.getElementById("post-container") switch (postData.action) { // actionごとに追加していく case "broadcast": let post = postData.post postContainer.innerHTML = postContainer.innerHTML + post break } } let userInput = document.getElementById("username") ... } |
サーバー側の処理と同じように、Switch文でアクションごとに処理を振り分けています。アクションを増やしたい時は、caseを追加していってください。
動作確認
動作確認をしましょう。
まずは、下記のコマンドで、Webサーバーを起動してください。
1 2 3 4 5 |
// mux / linux make run // Windows go run cmd/web/*.go |
次に、ブラウザから「http://localhost:8080/」へアクセスし、掲示板から投稿してみましょう。

いい感じですね。
サーバーリコネクト機能(おまけ)
最後におまけとして、サーバーのリコネクト機能を実装します。
サーバーを再起動などすると、全てのユーザーのコネクションは当然ながら切れてしまいます。そして、それは何かと不便なため、サーバーへリコネクトする機能を実装しましょう。
「joewalnes/reconnecting-websocket」を使用すると簡単に実装できます。
ソースコードをダウンロードすると「reconnecting-websocket.min.js」ファイルが格納されているので、それをstaticフォルダに格納してください。
それから、格納したファイルをhome.jetファイルから読み込みます。
1 2 3 4 5 6 7 8 9 10 |
<!-- home.jet --> <!DOCTYPE html> ... <script src="/static/reconnecting-websocket.min.js"></script> <script src="/static/scripts.js"></script> </body> </html> |
必ず、scripts.jsより前に読み込んでください。
そして、scripts.jsのWebSocketsの作成を以下のように変更しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// scripts.j document.addEventListener('DOMContentLoaded', function(){ // WebSocketsオブジェクトの作成 socket = new ReconnectingWebSocket( "ws://127.0.0.1:8080/ws", null, { debug: true, reconnectInterval: 3000 // 3s後に再接続 }) ... } |
一度、サーバーをCtrl + cでストップし、 サーバーを立ち上げなおすと、動作を確認することができます。
1 2 3 4 5 6 7 8 9 10 |
$ make run go run cmd/web/*.go 2021/04/22 07:23:31 Starting channel listener 2021/04/22 07:23:31 Starting web server on port 8080 ^Csignal: interrupt <<< サーバーを止める # 立ち上げ直す $ make run go run cmd/web/*.go 2021/04/22 07:23:44 Starting channel listener |

サーバーを落としたあと「WebSocket connection to ‘ws://127.0.0.1:8080/ws’ failed」となり、立ち上げ直すと「Successfully connected」と表示されたので、リコネクト機能が正常に動作したことがわかりますね^^
おわりに
WebSocketsは、面白いですね。結構慣れてきたので、DBやクリーンアーキテクチャ、Reactなどを絡めてより高度なアプリケーションの作成にチャレンジしたいと思います^^
それでは、また!
コメントを残す
コメントを投稿するにはログインしてください。