
Socket.IOは、リアルタイム双方向通信を実現するJavaScriptライブラリなのですが、FlaskとFastAPIでは実装がまた別でここも作り直しになります
時間のない人向けの最終形態は githubへ
github.com
最小の実装
バージョン情報
バックエンドの requirement.txt
Pythonバージョン: 3.11
fastapi==0.104.1 uvicorn[standard]==0.24.0 python-socketio==4.3.1 python-engineio==3.9.0 python-multipart==0.0.6
フロントエンドの package.json
Node.jsバージョン: 18
{
"dependencies": {
"next": "^14.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"socket.io-client": "^4.7.2"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/socket.io-client": "^1.4.36",
"typescript": "^5.0.0",
"eslint": "^8.0.0",
"eslint-config-next": "^14.0.0"
}
}
FastAPIでの実装のとっかかり
from fastapi import FastAPI import socketio sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*") sio_app = socketio.ASGIApp(sio) app = FastAPI() app.mount("/socket.io", sio_app) # クライアントは /socket.io に接続 @sio.event async def connect(sid, environ): await sio.emit("welcome", {"msg": "hello"}, to=sid) if __name__ == "__main__": uvicorn.run(socket_app, host="0.0.0.0", port=8000)
SocketIO接続テスト環境の実装
最小限のテスト環境として、フロントエンドとバックエンドでSocketIOで接続と切断だけを行う簡単なテスト
バックエンド側の実装
main.py
'use client' import React, { useState } from 'react' import io from 'socket.io-client' type Socket = ReturnType<typeof io> export default function Home() { const [socket, setSocket] = useState<Socket | null>(null) const [connected, setConnected] = useState(false) const connectSocket = () => { if (socket && connected) { console.log('既に接続されています') return } console.log('SocketIO接続を開始します...') const newSocket = io(`${window.location.protocol}//${window.location.hostname}:8000`) newSocket.on('connect', () => { console.log('サーバーに接続しました') setConnected(true) }) newSocket.on('disconnect', () => { console.log('サーバーから切断されました') setConnected(false) }) newSocket.on('connect_error', (error: any) => { console.error('接続エラー:', error) setConnected(false) }) setSocket(newSocket) console.log('SocketIOオブジェクトを設定しました') } const disconnectSocket = () => { if (socket) { socket.close() setSocket(null) setConnected(false) console.log('手動で切断しました') } } return ( <div className="container"> <h1>SocketIO テスト環境 (Next.js App Router)</h1> <div className="card"> <h2>接続状態</h2> <div className={`status ${connected ? 'connected' : 'disconnected'}`}> {connected ? '接続中' : '切断中'} </div> <div style={{ marginTop: '10px' }}> <button onClick={connectSocket} className="button connect" disabled={connected} > 接続 </button> <button onClick={disconnectSocket} className="button disconnect" disabled={!connected} > 切断 </button> </div> </div> </div> ) }
フロントエンド
page.tsx
'use client'
import React, { useState } from 'react'
import io from 'socket.io-client'
type Socket = ReturnType<typeof io>
export default function Home() {
const [socket, setSocket] = useState<Socket | null>(null)
const [connected, setConnected] = useState(false)
const connectSocket = () => {
if (socket && connected) {
console.log('既に接続されています')
return
}
console.log('SocketIO接続を開始します...')
const newSocket = io(`${window.location.protocol}//${window.location.hostname}:8000`)
// 接続成功時の処理
newSocket.on('connect', () => {
console.log('サーバーに接続しました')
setConnected(true)
})
// 切断時の処理
newSocket.on('disconnect', () => {
console.log('サーバーから切断されました')
setConnected(false)
})
// エラー時の処理
newSocket.on('connect_error', (error: any) => {
console.error('接続エラー:', error)
})
setSocket(newSocket)
console.log('SocketIOオブジェクトを設定しました')
}
const disconnectSocket = () => {
if (socket) {
socket.close()
setSocket(null)
setConnected(false)
console.log('手動で切断しました')
}
}
return (
<div className="container">
<h1>SocketIO チャットルーム (Next.js App Router)</h1>
<div className="card">
<h2>接続状態</h2>
<div className={`status ${connected ? 'connected' : 'disconnected'}`}>
{connected ? '接続中' : '切断中'}
</div>
<div style={{ marginTop: '10px' }}>
<button
onClick={connectSocket}
className="button connect"
disabled={connected}
>
接続
</button>
<button
onClick={disconnectSocket}
className="button disconnect"
disabled={!connected}
>
切断
</button>
</div>
</div>
</div>
)
}
接続テスト
ブラウザでhttp://localhost:3000へ接続
「接続」ボタンをクリックし、状態が「接続中」に変わることを確認

「切断」ボタンをクリックし、状態が「切断中」に変わることを確認

socketio バージョンによって、テストがうまくいかないことがあるので
うまくいかない時は、フロントエンドとバックエンドのバージョンを変えてテストを色々試してみるとよいです
今回の構成では、python-socketio 5.13.0(Engine.IO 4.xプロトコル)とsocket.io-client 4.7.2(Engine.IO 4.xプロトコル)を使用しており、互換性がある
チャットルーム実装
SocketIOでは双方向通信できるので、クライアント同士を「部屋(room)」に参加させて、同じ部屋にいる人だけにメッセージを届けるちょっとしたチャットルームを作ることができます
チャットルーム基本の流れ
1.クライアントが接続する
接続時にユーザーを特定の「room」に参加させる
2.メッセージを受信する
受け取ったメッセージを「同じroom」にいる人だけにブロードキャストする
3.退出処理する
切断時や退出時にsocketIO切断
おわりに
まだFastAPIによりパフォーマンス向上の実感はないですが、バックエンドをリファクタリングしつつ
テスト用プログラムも実装していきたい。。。
Kubernetes化への道のりは、まだもう少し時間がかかりそう