背景:プログラミング用教材を開発している。当初、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への格上げの過程が分かるのだろうけど。。