chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

Flask<---ws--->JSの組み合わせでアプリを試作

背景:プログラミング用教材を開発している。当初、JSで書いた家電Emulatorとクラウド上のサーバ(ECHONET Web API)間の接続をMQTTで実現する予定であった
課題:MQTTだと、AWS IoTぐらいにセキュリティ管理できていないと、横から勝手にTopicを決めてメッセージをPublishして他人の家(Emulator)の家電を操作できてしまう。教材とは言え、セキュリティ的に緩すぎるのは避けたい。
対策:サーバ(ECHONET Web API)はクラウドにおかず、生徒一人一人のラズパイ上に構築する。となると、MQTT使うメリットが薄まるので、クライアント・サーバ間はWebSocketで接続する。自分に腕があれば、家電EmulatorとWebAPIのサーバ機能を一体に実装にするのだけど、性質の異なる2つのイベントループを一つのアプリで回すノウハウもないので、、これらは別アプリとする。

結構多くの人が、Flask/JavaScript間をWebSocketで接続するサンプルを提示してくれていて、まぁ情報は充実しているのだが、自分も理解のために試作してみた。以下はクライアントアプリとWebSocketで通信するサーバ側アプリの例(Flaskを利用)

#!/usr/bin/python3

#
# sudo python3 -m pip install flask_socketio
#
import time

from flask import Flask
from flask_socketio import SocketIO, emit   #, send, emit

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, cors_allowed_origins="*")
#sio.register_namespace(MyCustomNamespace('/test'))


@socketio.on('message')
def handle_message(data):
    print('received message: ',  data)
    emit('ack', "good opinion!")



@socketio.on('my event', namespace='/')  # namespace='/test')
def handle_my_custom_event(json):
    print('received json: ' , json)
    emit('ack', "good opinion!")

@socketio.on('hello', namespace='/')  # namespace='/test')
def hello_event(msg):
    print('received msg: ', msg)
    emit('ack', "nice to meet you!!")

@app.route("/")
def home():
    msg = "home page"
    return msg

@app.route("/broadcast")
def broadcast():
    emit('attention', ('who is', 'foo', 'bar', '???'), namespace='/', broadcast=True)  # namespace='/')
    msg = "broadcast"
    return msg

def my_function_handler2(msg):
    print('received msg 22222',  msg)
socketio.on_event('my event2', my_function_handler2, namespace='/')  # '/test')

if __name__ =="__main__":
    socketio.run(app, host="0.0.0.0", port=8085)

以下はJSで書いたClientアプリの例
file: simple_client.js

const URL="ws://192.168.10.100:8085"
var socket = io(URL);
function send(){
    t = new Date();
     socket.emit('message', {data: "message from client..."});  
     document.getElementById("log").innerHTML += ('send @' + t.getTime() + "<br>");
}
socket.on('connect', function() {
    socket.emit('hello', {data: 'im connected2!!'});
});
socket.on('ack', (msg) => { 
    t = new Date();
    document.getElementById("log").innerHTML += ('ack: ' + msg + t.getTime() + "<br>");
});
socket.on('hello', (msg) => { 
    t = new Date();
    document.getElementById("log").innerHTML += ('hello: ' + msg + "@" + t.getTime() + "<br>");
});
socket.on('attention', (msg) => { 
    t = new Date();
    document.getElementById("log").innerHTML += ('attention: ' + msg + "@" + t.getTime() + "<br>");
});

以下はHTMLの例、条件を厳しくするため、Flaskからは降らせず、ローカルPCにおいてブラウザに読み込ませている。

<html>
   <header>
       <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA==" crossorigin="anonymous"></script>
       <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
       <script src="simple_client.js"></script>
   </header>
   <body>
      <button type="button" onclick="send();"> send </button>
      <div id="log"></div>
   </body>
</html>

■メモ
良く分かっていない事
namespaceとは何なのか、ClientとServer間の接続を管理する識別名のようなのだが、使い方がイマイチわからない
今回の試作はsocketというライブラリを使っているのだが、これはWebSocketなのか? 単なる素のTCPのソケット通信ということはない?(多分ないだろうけど)。パケットキャプチャしてみたら、HTTPからWebSocketへの格上げの過程が分かるのだろうけど。。


■参考URL
Flask-SocketIO — Flask-SocketIO documentation