Flaskでwebsocketチャット


https://github.com/zeekay/flask-uwsgi-websocket/tree/master/examples/chat-gevent
https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/appengine/flexible/websockets
から書き直してみた。


![GIF.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330873/9a27dacf-0b50-cfb5-f3b1-9819cd78caf9.gif)



## ファイルツリー

```
chat/
    ├ static/
    |    js/
    |      └ main.js
    ├ templates/
    |      └ index.html
    ├ chat.py    
    └ requirements.txt
```

## ファイル

```js:main.js
(function () {

    function showMessage(message) {
        $('#messages').append('<li>' + message)
    }

    if (!window.WebSocket) {
        if (window.MozWebSocket) {
            window.WebSocket = window.MozWebSocket;
        } else {
            showMessage("Your browser doesn't support WebSockets")
        }
    }

    let scheme = window.location.protocol === "https:" ? 'wss://' : 'ws://';
    let webSocketUri = scheme
        + window.location.hostname
        + (location.port ? ':' + location.port : '')
        + $SCRIPT_ROOT + '/websocket';
    let ws = new WebSocket(webSocketUri);

    ws.onopen = function (evt) {
        showMessage('Connected to chat.')
    }

    ws.onmessage = function (evt) {
        showMessage(evt.data)
    }

    ws.onclose = function (evt) {
        $('#messages').append('<li>WebSocket connection closed.</li>');
    }

    $('#send-message').on("submit", function () {
        let message = $('#name').val() + ": " + $('#message').val();
        showMessage(message)
        ws.send(message);
        $('#message').val('').trigger('focus');
        return false;
    });
}());
```

```html:index.html
<!doctype html>
<head>
    <meta charset="utf-8"/>
    <title>WebSocket Chat Example</title>
    <style>
        li {
            list-style: none;
        }
    </style>
    <script type=text/javascript>
        $SCRIPT_ROOT =
        {{ request.script_root|tojson|safe }}
    </script>
</head>
<body>
<h2>WebSocket Chat Example</h2>
<form id="send-message">
    <input id="name" type="text" value="name">
    <input id="message" type="text" value="message"/>
    <input type="submit" value="Send"/>
</form>
<div id="messages"></div>
</body>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
        integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
        crossorigin="anonymous"></script>
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
</html>
```

```py:chat.py
import os
from collections import deque
from flask import Flask, render_template, url_for
from flask_uwsgi_websocket import GeventWebSocket

app = Flask(__name__)
ws = GeventWebSocket(app)
users = {}
backlog = deque(maxlen=10)


@app.context_processor
def override_url_for():
    return dict(url_for=dated_url_for)


def dated_url_for(endpoint, **values):
    if endpoint == 'static':
        filename = values.get('filename', None)
        if filename:
            file_path = os.path.join(app.root_path,
                                     endpoint, filename)
            values['q'] = int(os.stat(file_path).st_mtime)
    return url_for(endpoint, **values)


@app.route('/')
def index():
    return render_template('index.html')


@ws.route('/websocket')
def chat(ws):
    users[ws.id] = ws

    for msg in backlog:
        ws.send(msg)

    while True:
        msg = ws.receive()
        if msg is None:
            break
        if msg == b"":
            continue

        backlog.append(msg)
        for id in users:
            if id != ws.id:
                users[id].send(msg)

    del users[ws.id]
    return


if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=8080, gevent=100)
```

```txt:requirements.txt
click==7.1.2
Flask==1.1.2
Flask-uWSGI-WebSocket==0.6.1
gevent==20.6.2
greenlet==0.4.16
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
uWSGI==2.0.19.1
Werkzeug==1.0.1
zope.event==4.4
zope.interface==5.1.0
```

## インストール

```
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install requirements.txt
```

## 実行

```
$ python chat.py
```