リアルタイム通知をWebSocketを利用して実現してみる(FastAPI)

# 概要

Webアプリケーションでリアルタイム通知を実現したいと考えて調査。
WebSocketを利用すればできそうなのでチャレンジする。

## 作ってみるもの

* WebSocketのサーバを作って、クライアントにリアルタイムに通知を送る
* クライアントからメッセージを受け取ったら、単純にそれを全クライアントに送信する

# 結論

pythonのフレームワーク「FastAPI」を利用してWebsocketのサーバを作成できた。
クライアントとして「wscat」を利用して動作確認もできた。
メッセージを複数クライアントにリアルタイムで通知できることが確認できた。

↓が動作確認の様子。

![20201205.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273897/32e65121-40fa-8ff2-3aab-08f00af2f018.gif)

# 詳細

## サーバの処理を作成

pythonのフレームワーク「FastAPI」を利用してサーバを作ることにする。
これを使うと結構、簡単に実現できる。

```main.py
from fastapi import FastAPI
from starlette.websockets import WebSocket
import json

# 接続中のクライアントを識別するためのkeyを格納
clients = {}

app = FastAPI()


@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
    '''
    WebSocket用のエンドポイント
    '''
    await ws.accept()
    # クライアントを識別するためのkeyを取得
    key = ws.headers.get('sec-websocket-key')
    clients[key] = ws

    try:
        while True:
            # クライアントからメッセージを受信
            message_data = await ws.receive_text()

            # 全クライアントにメッセージを送信
            json_data = {
                "key": key,
                "message_data": message_data,
            }
            for client in clients.values():
                await client.send_text(json.dumps(json_data))

    except:
        await ws.close()
        # 接続が切れたら、当該クライアントを削除
        del clients[key]
```
 
## 簡易サーバ起動

uvicornで簡易サーバを起動する。
ポートは8002を指定。

```
>uvicorn main:app --port 8002
INFO:     Started server process [25564]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8002 (Press CTRL+C to quit)
```

## 動作確認に使うクライアント「wscat」をインストール

WebSocketの動作確認には「wscat」がいいらしいのでインストール。

```
>npm install -g wscat
```

## 「wscat」でサーバに接続して動作確認

↓のような感じで、対話式で入力したものがサーバに送信される。
送信後に、受信した文字列が表示される。
想定通りのjsonが返ってきているのでOK！

```
>wscat -c ws://localhost:8002/ws
Connected (press CTRL+C to quit)
> test
< {"key": "j7+ykyHbyODGX7Q8QnaTUA==", "message_data": "test"}
> abc
< {"key": "j7+ykyHbyODGX7Q8QnaTUA==", "message_data": "abc"}
> 123456
< {"key": "j7+ykyHbyODGX7Q8QnaTUA==", "message_data": "123456"}
> hello Websocket!
< {"key": "j7+ykyHbyODGX7Q8QnaTUA==", "message_data": "hello Websocket!"}
>
```

## クライアントを2つにして動作確認

クライアントを2つ起動して動かしてみる。
入力したメッセージが2つのクライアントで受信されることが確認できる。
動きはOK！

```
クライアント-1
>wscat -c ws://localhost:8002/ws
Connected (press CTRL+C to quit)
> abc
< {"key": "/fQVRjMkuHfxdT73zH5H+g==", "message_data": "abc"}
< {"key": "JnxFx5Eq98RFckkDxJ9iBw==", "message_data": "123"}
< {"key": "JnxFx5Eq98RFckkDxJ9iBw==", "message_data": "456"}
< {"key": "JnxFx5Eq98RFckkDxJ9iBw==", "message_data": "789"}
> hello!
< {"key": "/fQVRjMkuHfxdT73zH5H+g==", "message_data": "hello!"}
> ok ok ok
< {"key": "/fQVRjMkuHfxdT73zH5H+g==", "message_data": "ok ok ok"}
```

```
クライアント-2
>wscat -c ws://localhost:8002/ws
Connected (press CTRL+C to quit)
< {"key": "/fQVRjMkuHfxdT73zH5H+g==", "message_data": "abc"}
> 123
< {"key": "JnxFx5Eq98RFckkDxJ9iBw==", "message_data": "123"}
> 456
< {"key": "JnxFx5Eq98RFckkDxJ9iBw==", "message_data": "456"}
> 789
< {"key": "JnxFx5Eq98RFckkDxJ9iBw==", "message_data": "789"}
< {"key": "/fQVRjMkuHfxdT73zH5H+g==", "message_data": "hello!"}
< {"key": "/fQVRjMkuHfxdT73zH5H+g==", "message_data": "ok ok ok"}
```

次はクライアントの画面を作ってみることにする。