LoginSignup
46
28

More than 3 years have passed since last update.

FastAPIでWebSockets

Last updated at Posted at 2020-02-24

TL;DR

  • FastAPIでAPI作成は簡単だけど、WebSocketsも簡単に実装できちゃう
  • FastAPI+WebSocketsで簡易チャットアプリの作成
  • チャットアプリではブロードキャストを実装

デモ

demo.gif

入力したメッセージは送信ボタンを押すと、送信元のIDと入力メッセージを全ブラウザにブロードキャストする

環境構築

必要な知識

  • dockerとdocker-composeがインストールされていること
  • Pythonのちょっとした知識
  • WebSocketsのちょっとした知識

ファイル構成

$ tree
.
├── api
│   └── main.py
├── app
│   ├── app.js
│   └── index.html
├── docker
│   ├── node
│   │   └── Dockerfile
│   └── uvicorn
│       ├── Dockerfile
│       └── requirements.txt
└── docker-compose.yml

インストール

# コードをクローンする
$ git clone https://github.com/sattosan/sample-fastapi-websockets.git

# クローンしたディレクトリへ移動
$ cd ./sample-fastapi-websockets

# コンテナの起動
$ docker-compose up -d --build

Webサーバにアクセス

サーバ側の実装 : FastAPI

./api/main.py
from fastapi import FastAPI
from starlette.websockets import WebSocket

app = FastAPI()

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

# WebSockets用のエンドポイント
@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
    await ws.accept()
    # クライアントを識別するためのIDを取得
    key = ws.headers.get('sec-websocket-key')
    clients[key] = ws
    try:
        while True:
            # クライアントからメッセージを受信
            data = await ws.receive_text()
            # 接続中のクライアントそれぞれにメッセージを送信(ブロードキャスト)
            for client in clients.values():
                await client.send_text(f"ID: {key} | Message: {data}")
    except:
        await ws.close()
        # 接続が切れた場合、当該クライアントを削除する
        del clients[key]

FastAPI公式ドキュメントでは、クライアントとサーバの双方向通信のサンプルが紹介されているが、今回は、サーバと接続が確立している全クライアントに対してブロードキャストを行いたかったため、clientsという辞書の中に、接続されたクライアントを識別する一意のkeyを格納している

メッセージがサーバ側に送られて来たとき、
keyを利用して接続しているクライアントそれぞれに受け取ったメッセージを送っている

# 接続中のクライアントそれぞれにメッセージを送信(ブロードキャスト)
for client in clients.values():
    await client.send_text(f"ID: {key} | Message: {data}")

フロント側の実装 : Node.js

./app/app.js
// モジュールのロード
const http = require('http');
const fs = require('fs');

// サーバーオブジェクトの作成
const server = http.createServer((req,res)=>{
  // ファイル読み込み
  fs.readFile('index.html','UTF-8',
    (error, data) => {
        // エラー処理
        if (error) {
            response.writeHead(500, {"Content-Type": "text/plain"});
            response.write("500 Internal Server Error\n");
            res.end();
        }
        // index.htmlを表示
        res.writeHead(200,{'Content-Type':'text/html'});
        res.write(data);
        res.end();
    });
});

// 待ち受け開始
server.listen(8080);
console.log('Server running');

Node.jsを使って簡易的なWebサーバを立てている
ここでは便宜上Node.jsを使っているが、もちろん他のWebサーバに代用可能

./app/index.html
<!DOCTYPE html>
<html>
    <head>
        <title>Chat</title>
    </head>
    <body>
        <h1>WebSocket Chat</h1>
        <form action="" onsubmit="sendMessage(event)">
            <input type="text" id="messageText" autocomplete="off"/>
            <button>Send</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
            var ws = new WebSocket("ws://localhost:8000/ws");
            ws.onmessage = function(event) {
                var messages = document.getElementById('messages')
                var message = document.createElement('li')
                var content = document.createTextNode(event.data)
                message.appendChild(content)
                messages.appendChild(message)
            };
            function sendMessage(event) {
                var input = document.getElementById("messageText")
                ws.send(input.value)
                input.value = ''
                event.preventDefault()
            }
        </script>
    </body>
</html>

FastAPIで作成したWebSocketsのエンドポイント(ws://localhost:8000/ws)を指定している
Submit!ボタンが押されたとき、FastAPIに入力したテキストメッセージを送信

逆に、ソケットからメッセージを受け取った場合、ulタグに受け取ったメッセージをliタグごと挿入する

おわりに

FastAPIでWebSocketsを使って簡単なチャットアプリ実装できた
今後は、namespaceを利用してルームごとにメッセージの送受信ができるように拡張したい

46
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
28