TL;DR
WebSocketをほとんど使かったことがなかったので使ってみた。
ユーザー認証のない簡易的なチャットアプリならすぐ作れそうだったので2018年にリリースされたばかりのPythonのWebフレームワークResponderを使って実装してみた。(そもそもResponderの情報が少なすぎる上にWebSocketを使っている記事などが皆無だったのでソースコード読んだりResponderのベースになっているStarletteのことも調べたりして結構大変だった…)
ResponderはPython界隈では有名なKenneth Reitzさん作(requestsやpipenv作った人)
完成品 : https://github.com/ksk001100/responder-websocket
プロジェクト構成
$ tree
.
├── Pipfile
├── Pipfile.lock
├── app.py
└── static
└── index.html
プロジェクトの作成
今回もpipenv
でプロジェクトを作成していきます。
$ mkdir responder-websocket-chat
$ cd responder-websocket-chat
$ pipenv --python 3.6.5
$ pipenv install responder --pre
サーバー実装
app.py
にサーバープログラムを書いていきます。
app.py
import responder
api = responder.API()
clients = {} # 1
@api.route('/ws', websocket=True)
async def websocket(ws):
await ws.accept()
key = ws.headers.get('sec-websocket-key') # 2
clients[key] = ws # 3
try:
while True:
msg = await ws.receive_text()
for client in clients.values(): # 4
await client.send_text(msg)
except:
await ws.close()
del clients[key] # 5
api.add_route('/', static=True)
api.run()
-
1の変数
clients
は接続中のクライアントを格納する辞書。メッセージを受け取った時に全てのクライアントにメッセージをブロードキャストするために格納している。 - 2でリクエストヘッダーからキーを取得して3で1で宣言した辞書にクライアントを格納する。
- 4の処理で接続クライアント全てにメッセージをブロードキャスト
- 5の例外処理部分は接続が切れたりリロードされたりした場合辞書に接続されてないクライアントが溜まってメモリを圧迫するのでクライアントを削除する処理
フロント実装
JSの処理部分だけ書きます。めんどくさいので実際はstatic/index.html
にscriptタグを直接埋め込んでいます。
const ws = new WebSocket('ws://localhost:5042/ws');
// メッセージを入力するinput要素
const textbox = document.getElementById('textbox');
// チャットのメッセージを表示するのul要素
const chat = document.getElementById('chat');
// 1
ws.onmessage = function (e) {
// メッセージのli要素作成
const li = document.createElement('li');
li.textContent = e.data;
chat.appendChild(li);
};
// 2
window.onload = function () {
textbox.addEventListener('keypress', function (e) {
// エンターキーが押された場合メッセージを送信
if (e.keyCode == 13) {
ws.send(textbox.value);
textbox.value = "";
}
});
}
- 1はサーバーからブロードキャストされたときの処理
- 2はメッセージを入力してエンターキーを押したときの処理