親プレイヤーが開いた場に子プレイヤーが参加した際に、参加中にプレイヤーに対して通知を行って画面更新を行わたい。それにはWebSocketによる双方向通信が必要だ。
サーバー側(Flaskアプリケーション)は、Flask-SocketIO
を用いる。まずはインストール。
(venv) $pip install flask-socketio
インポート(1行目)とインスタンス生成(6行目)。
from flask_socketio import SocketIO, emit
import uuid
app = Flask(__name__, static_folder="public", static_url_path="/")
app.secret_key = "planningpoker"
socketio = SocketIO(app)
場への参加は、通常のHTTPアクセスに対するハンドラが処理を受ける。12行目で、WebSocket通信によるブロードキャストを行う関数を呼び出している。
@app.route("/table/<table_id>/join", methods=["POST"])
def join_do(table_id):
player_name = request.form["nickname"]
table = tables[table_id] # 場の情報
players = table["PLAYERS"]
player_id = len(players) + 1 # 現在の人数+1
player = (player_id, player_name)
players.append(player)
session["ses_player_id"] = table_id + "." + str(player_id)
broadcast_join(players)
return redirect(f"/table/{table_id}")
broadcast_join
関数の定義は以下のとおり。
def broadcast_join(players):
print("broadcasting on player joinning..")
player_names = [name for (id, name) in players]
payload = {"players": player_names}
socketio.emit("update", payload, broadcast=True)
JSONのペイロードを作成し、SocketIO.emit
を呼び出してイベント送出を行う。第1引数はイベント名を表す。
次にクライアント側。こちらはSocket.io
を利用するのでまずはインストール。
$yarn add socket.io
$yarn add -D @types/socket.io-client
tsxファイルでインポート。
import io from 'socket.io-client'
Socket
の作成。これはどこに定義すべきか悩ましいが、とりあえず関数コンポーネントの外側に定義しておいた。
const socket: SocketIOClient.Socket = io();
WebSocketのイベント待受けは副作用なので、useEffect
内で行う。
export const TablePage: FunctionComponent<TableProps> = (props) => {
const [bidding, setBidding] = useState(true);
const [players, setPlayers] = useState<PlayerProps[]>([]);
// ...
useEffect(() => {
socket.on("update", (data: any) => {
const players = mapPlayers(data.players);
console.log(`Got ${players.length} players on receiving an update event.`)
setPlayers(players);
});
}, []);
socket.on
でイベント名(update
)を指定してコールバックを登録する。setPlayers
はuseState
を用いて作成したステート変数更新関数であり、これを呼び出してWebSocketで受け取ったプレイヤー情報を更新する(12行目)。
イベントハンドラ登録は1回だけ行うようにしたいので、useEffect
の第2引数にはから配列を渡している(13行目)。
WebSocketによる最初の双方向通信が実現できた。
ここまで開発したアプリケーションだが、ローカルでは問題なく動作するのにHerokuにデプロイするとうまく動かない。開いた場の情報をapp.py
に格納しているのが問題かもしれない。
Flaskでのアプリケーションスコープの状態管理がよくわからないので、Redis
に格納するようにしようかと思う。次はその辺の実装だ。
コメント