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