TL;DR

WebSocketをほとんど使かったことがなかったので使ってみた。 ユーザー認証のない簡易的なチャットアプリならすぐ作れそうだったので2018年にリリースされたばかりのPythonのWebフレームワークResponderを使って実装してみた。(そもそもResponderの情報が少なすぎる上にWebSocketを使っている記事などが皆無だったのでソースコード読んだりResponderのベースになっているStarletteのことも調べたりして結構大変だった…)

ResponderはPython界隈では有名なKenneth Reitzさん作(requestspipenv作った人)

完成品 : 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にサーバープログラムを書いていきます。

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でリクエストヘッダーからキーを取得して31で宣言した辞書にクライアントを格納する。
  • 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はメッセージを入力してエンターキーを押したときの処理

実際の動作

resp.gif