Go WebSocketを使ってみる

今回は Go言語 で WebSocket を使用してみたいと思います。

軽く感じをつかむだけなので、あまり手の込んだものは作りませんｗ

## やりたいこと

サーバー と クライアント で メッセージ を キャッチボール する。

・・・のを WebSocket を使ってやります。

~~これくらいなら ajax とかでいい感じもしますが、、、~~

## WebSocket とは？

下記の資料がわかりやすかったです。

https://www.slideshare.net/You_Kinjoh/websocket-10621887

理解した感じでざっくり説明すると、
Webのアクセスは、キャッチボールみたいな感じで

ブラウザ「〇〇のページをちょうだい」（要求）

サーバー「はいよ！」（応答）

で接続が切れてしまうのですが、

WebSocket を使用すると 電話のように常時つなぎっぱなしにできます。

利点としては、先ほどのキャッチボールを始める前に
いろいろな前処理がなくなるので応答が早くなります。

リアルタイム性を求めるものによく使われている感じですね。

## クライアント側

まずは投げるほうを実装してみます。

今回は webブラウザ から投げるようにしようと思います。

JavaScript 側で WebSocket が使えるようになっているので、
いたせりつくせりです。。。

https://developer.mozilla.org/ja/docs/Web/API/WebSocket

ページのJavaScript部分を切り出すとこんな感じ↓

```javascript
      var socket = null;

      // サーバーに接続する (一連の処理)
      window.onload = function () {
        socket = new WebSocket("ws://localhost:3000/ws");
        socket.onopen = function () {
          append_message("system", "connect to server");
        };
        socket.onmessage = function (event) {
          // サーバーからメッセージを受け取る
          append_message("server", event.data);
        };
      };

      // メッセージ欄を更新する
      function append_message(name, message) {
        let li_name = document.createElement("li");
        let name_txt = document.createTextNode(name);
        li_name.appendChild(name_txt);

        let ul_message = document.createElement("ul");

        let li_message = document.createElement("li");
        let message_txt = document.createTextNode(message);
        li_message.appendChild(message_txt);

        ul_message.appendChild(li_message);

        li_name.appendChild(ul_message);

        let ul = document.getElementById("messages");
        ul.appendChild(li_name);
      }

      // サーバーにメッセージを送信する
      function send() {
        let send_msg = document.getElementById("message");
        let msg = send_msg.value;
        if (msg == "") {
          return;
        }
        socket.send(msg);
        append_message("you", msg);
        send_msg.value = "";
      }
```

`window.onload` でページが読み込まれたら、サーバーにアクセスしにいきます。
`socket = new WebSocket("ws://localhost:3000/ws");`というところで
`socket`というものを軸にやりとりできます。

`socket.onopen` で初回アクセス時に走る処理が書けます。
`socket.onmessage` でサーバーから投げ返されてきたものが入ってきます。

`append_message()` は、ページの表示を変更するときに使います。

`send()` は、メッセージをサーバーに送信するときに使います。
送るときは、`socket.send("文字")`で送れます。

ページ部分はこんな感じ

```html:index.tmpl
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>sample</title>
    <script type="text/javascript">
      // script(↑に記載してるもの)
    </script>
  </head>
  <body>
    <!-- 送信欄 -->
    <p>メッセージを入力してください。</p>
    <input type="text" id="message" /><button onclick="send()">送信</button>

    <!-- メッセージ欄 -->
    <p>メッセージ一覧</p>
    <ul id="messages"></ul>
  </body>
</html>
```

しんぷる！

## サーバー側

ページからアクセスされるときに返したり、WebSocket接続の受け口を作ります。

```go
package main

import (
	"embed"
	"fmt"
	"log"
	"net/http"
	"text/template"

	"golang.org/x/net/websocket"
)

//go:embed index.tmpl
var indexTmpl embed.FS

func main() {
	http.HandleFunc("/", index)
	http.Handle("/ws", websocket.Handler(msgHandler))

	err := http.ListenAndServe(":3000", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func index(w http.ResponseWriter, r *http.Request) {
	tmpl, err := template.ParseFS(indexTmpl, "index.tmpl")
	if err != nil {
		log.Fatal(err)
	}
	err = tmpl.Execute(w, "")
	if err != nil {
		log.Fatal(err)
	}
}

func msgHandler(ws *websocket.Conn) {
	defer ws.Close()

	// 初回のメッセージを送信
	err := websocket.Message.Send(ws, "こんにちは！ :)")
	if err != nil {
		log.Fatalln(err)
	}

	for {
		// メッセージを受信する
		msg := ""
		err = websocket.Message.Receive(ws, &msg)
		if err != nil {
			log.Fatalln(err)
		}

		// メッセージを返信する
		err := websocket.Message.Send(ws, fmt.Sprintf(`%q というメッセージを受け取りました。`, msg))
		if err != nil {
			log.Fatalln(err)
		}
	}
}
```

`websocket.Message.Receive()` で 送信されてきたメッセージを受信して
`websocket.Message.Send()` で クライアント側に送信する。

## 試してみる

`go build` で exe を作って実行する。

ページアクセス時
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/238600/673d2345-4179-89e9-b233-f4fc8a85a951.png)

「ほげほげ」メッセージを送信すると・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/238600/13ee23a7-64b3-ed37-8b84-b06b04fcc6ac.png)

そのままの言葉を返してくれる感じです。

送信したあと、ラグがなくてパっと返ってきます。

ブラウザの開発者ツールで見てみると、WebSocketで接続されているのがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/238600/7cd86efe-c804-5363-e292-d9faa073d736.png)

## まとめ

ほんとさわりだけでしたが、WebSocketを触れることができました。

リアルタイムな処理が必要になるときは検討してみるとよいかもしれませんね！
ぜひ使ってみてくださいね～。