Websocketの使用#

Websocketを使用するには、ルート関数ではなく、websocket関数を以下のように宣言します。

@app.websocket('/ws')
async def ws():
    while True:
        data = await websocket.receive()
        await websocket.send(data)

websocketrequest と同様にグローバルであり、headers など、多くの同じ属性を共有します。

Websocketの手動での拒否または承認#

Websocket接続は、HTTPアップグレードリクエストを受け入れることによって作成されますが、サーバーはWebsocketリクエストを拒否することを選択できます。そのためには、ルート関数の場合と同様に、websocket関数から戻ります。

@app.websocket('/ws')
async def ws():
    if (
        websocket.authorization.username != USERNAME or
        websocket.authorization.password != PASSWORD
    ):
        return 'Invalid password', 403  # or abort(403)
    else:
        websocket.accept()  # Automatically invoked by receive or send
        ...

送受信の独立化#

最初の例では、サーバーが応答するために、クライアントがメッセージを送信する必要があります。送受信を独立して行うには、独立したタスクが必要です。

async def sending():
    while True:
        await websocket.send(...)

async def receiving():
    while True:
        data = await websocket.receive()
        ...

@app.websocket('/ws')
async def ws():
    producer = asyncio.create_task(sending())
    consumer = asyncio.create_task(receiving())
    await asyncio.gather(producer, consumer)

gather行は重要です。これがないと、websocket関数が戻り、QuartがHTTPレスポンスを送信するトリガーとなります。

切断の検知#

クライアントが切断すると、CancelledError が発生します。これをキャッチして、切断を処理できます。

@app.websocket('/ws')
async def ws():
    try:
        while True:
            data = await websocket.receive()
            await websocket.send(data)
    except asyncio.CancelledError:
        # Handle disconnection here
        raise

警告

CancelledError は再送する必要があります。

接続の終了#

適切なWebsocketエラーコードを使用して close メソッドを待つことで、接続を閉じることができます。

@app.websocket('/ws')
async def ws():
    await websocket.accept()
    await websocket.close(1000)

websocketが承認される前に閉じられた場合、サーバーは403 HTTPレスポンスで応答します。

Websocketのテスト#

Websocketルートをテストするには、以下のようにtest_clientを使用します。

test_client = app.test_client()
async with test_client.websocket('/ws/') as test_websocket:
    await test_websocket.send(data)
    result = await test_websocket.receive()

Websocketルートがレスポンスを返した場合、test_clientは、response 属性を持つ WebsocketResponseError 例外を発生させます。例えば、

test_client = app.test_client()
try:
    async with test_client.websocket('/ws/') as test_websocket:
        await test_websocket.send(data)
except WebsocketResponseError as error:
    assert error.response.status_code == 401

バイトまたは文字列の送受信#

WebSocketプロトコルでは、バイトまたは文字列をフレームマーカーで送信できます。どのマーカーが送信されたかを示します。receive() メソッドは、クライアントが送信した内容に応じて、bytes または str を返します。つまり、クライアントが文字列を送信した場合、メソッドから返されます。同様に、バイトまたは文字列を送信できます。

WebsocketルートとHTTPルートの混在#

Quartでは、WebsocketとHTTPリクエストの両方に対してルートを定義できます。これにより、リクエストのタイプ(WebSocketアップグレードかどうか)に応じてレスポンスを送信できます。以下のように。

@app.route("/ws")
async def http():
    return "A HTTP request"

@app.websocket("/ws")
async def ws():
    ...  # Use the WebSocket

HTTP定義がない場合、Quartは、不足しているルートへのリクエストに対して、404ではなく、400、Bad Requestレスポンスで応答します。