WebSocket使ってみた(チャットツールを作ってみる)

# やること
チャットツールを作ってみたいということでとりあえず試しにChatGPTに作ってもらってそこから読み解いてみようとしました。
# WebSocketとは
https://qiita.com/south37/items/6f92d4268fe676347160

ここに詳しくまとめてくださってるので読んでみましょう。
とりあえず双方向の通信を行えるかつ、低コストの通信ができるそうです。
今回私が作ろうとしたチャットツールはチャット情報に揮発性を持たせたかったので、採用しました。
(つまりWebSocketだけだとクライアントとつなげた後の情報しか得られないので履歴とかを残したいならDBとか一緒に使うのがいいのかな)

# 今回ChatGPTに作ってみてもらったもの
https://github.com/takaryo1010/webSocketChat
React(TypeScript)とGoを使ってクライアントとサーバーを作りました。

# 読解　~サーバー編~

```main.go
func main() {
	// ルートディレクトリ
	fs := http.FileServer(http.Dir("./public"))
	http.Handle("/", fs)

	// WebSocket接続
	http.HandleFunc("/ws", handleConnections)

	go handleMessages()

	fmt.Println("Chat server started on :8080")
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println("error:", err)
	}
}
```
とりあえず、main.goから
ルートディレクトリに関しては./publicに何も置いていないので飛ばします。
localhost/wsで`handleConnecitons`を呼び出してますね。
次に`handleMessages`。goroutineが使われています。一度にたくさんリクエストが来たときに処理する工夫でしょう。

そしたら、出てきた二つの関数を見ていきます。
```main.go
// WebSocketアップグレード用
var upgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

type roomMessage struct {
	room    string
	message string
}
type Message struct {
	Text string `json:"text"`
}

var rooms = make(map[string]map[*websocket.Conn]bool)
var roomMessages = make(chan roomMessage)


func handleConnections(w http.ResponseWriter, r *http.Request) {
	// WebSocket接続をアップグレード
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer ws.Close()

	// ルーム名を取得（例: クエリパラメータ "room"）
	room := r.URL.Query().Get("room")
	if room == "" {
		room = "default" // デフォルトのルーム
	}

	// ルームにクライアントを追加
	if rooms[room] == nil {
		rooms[room] = make(map[*websocket.Conn]bool)
	}
	rooms[room][ws] = true

	for {
		var msg Message
		err := ws.ReadJSON(&msg)
		if err != nil {
			fmt.Println("error reading JSON:", err)
			fmt.Println("Received data:", msg)
			delete(rooms[room], ws)
			break
		}
		// メッセージをルームにブロードキャスト
		roomMessages <- roomMessage{room: room, message: msg.Text}
	}
}
```

上から読んでいきます。
WebSocketをアップグレード...?
http(s)接続を普段は使っていますが、そこからWebSocket用にアップグレードが必要らしいです。これによってプロトコルが変わり、接続方法を決定できるらしいです。後述しますが、リクエスト方法も`http://localhost:8080`から`ws://localhost:8080`に変更されます！http以外に初めて出会えてうれしいです。

次にクエリパラメータにroom名を入れて通信するのでそれを取ってきます。
そこからroomsに追加していきます。
```main.go
var rooms = make(map[string]map[*websocket.Conn]bool)
```
ということでmapを使って誰が接続しているかチェックしているっぽいですね。

最後にfor無限ループでメッセージを受信しています。json形式でメッセージのやり取りをしているので、`ws.ReadJSON`を使ってテキストを持ってきています。
うまくいったら最後に`roomMessages`というチャネルに送ります。
どこでそのチャネルを受け取るのか、それは先ほど`go`を付けていた`handleMessages`です！

```main.go
func handleMessages() {
	for {
		// ブロードキャストチャンネルからメッセージを取得
		msg := <-roomMessages
		// ルームに接続しているすべてのクライアントに送信
		for client := range rooms[msg.room] {
			err := client.WriteJSON(msg.message)
			if err != nil {
				fmt.Println("error:", err)
				client.Close()
				delete(rooms[msg.room], client)
			}
		}
	}
}
```
for無限ループで`roomMessages`に値が入るまで待っている処理をしています。
`msg`に入ったら処理が始まります。`WriteJSON`を使うことによって`rooms`に入っているクライアントさん全員に対してJSON形式で送信できます。

バックエンドに関しては以上です。
面白かったことは
- ゴルーチン使ってる
- ws通信(ws://----)
だったなと。

# 読解　~クライアント編~
```App.tsx
  useEffect(() => {
    // WebSocketサーバーに接続
    const ws = new WebSocket(`ws://localhost:8080/ws?room=${room}`);

    // 接続が開いたときのイベント
    ws.onopen = () => {
      console.log("WebSocket connected");
      setIsConnected(true);
    };

    // メッセージを受信したとき
    ws.onmessage = (event) => {
      setMessages((prev) => [...prev, event.data]);
    };

    // 接続が閉じたとき
    ws.onclose = () => {
      console.log("WebSocket disconnected");
      setIsConnected(false);
    };

    setSocket(ws);

    // クリーンアップ
    return () => {
      ws.close();
    };
  }, [room]); // ルームが変更されたときに再接続

```
WebSocket型にはいろいろメソッドがあるっぽいですね。

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

今回は
- onopen
- onmessage
- onclose
を使っています。
接続はいいとして、messageを受信した時を見ます。
eventデータをセットするようにします。その後送信ボタンを押した際に以下の関数を実行し、サーバーに送ります。

```App.tsx
  const sendMessage = () => {
    if (socket && isConnected && input.trim() !== "") {
      const msg = { text: input }; // メッセージをオブジェクト形式で送信
      socket.send(JSON.stringify(msg)); // JSONとして送信
      setInput("");
    } else {
      console.log("WebSocket is not connected or input is empty");
    }
  };
```
またWebSocketのメソッドを使います。今回は`send`。これでJSONを送ります。

クライアントはこれだけです。
フロントエンドが苦手な私でも理解できる程度でした。


# まとめ
クライアントとサーバーどちらもChatGPTに作ってもらいましたが、技術選定するため、試作品を作るときにすごく便利ですね。
なんとなく双方向通信って難しそうって思っていたので実装できて面白かったです。
私はこれを使って開発を進めるので、皆さんもぜひ作ってみてください！！！