Tipy a triky
15.08.2018
Miroslav Beka
Websockety ve Flasku
Co jsou WebSocketyWebSocket je komunikační protokol, který umožňuje obousměrnou (full-duplex) komunikaci mezi klientem a serverem v reálném čase.
Na rozdíl od klasického HTTP spojení, které funguje na principu request → response, WebSocket udržuje trvalé spojení, přes které mohou klient i server posílat data kdykoli.
WebSockety se často používají v aplikacích jako:
• chat aplikace
• live notifikace
• online hry
• real-time dashboardy
• finanční aplikace s aktuálními daty
Ve Flask aplikacích se WebSockety nejčastěji implementují pomocí knihovny Flask-SocketIO.
WebSockety ve FlaskuPokud ses někdy setkal s výrazem websocket a chtěl by ses dozvědět, co to vlastně je a jak se to používá v Python aplikaci, tak tento článek je právě pro tebe.
Standardně tvůj prohlížeč komunikuje na webu pomocí http protokolu. Klasický http protokol nabízí jednoduchou komunikaci. Pošle se request a jako odpověď dostanu response. Tento klasický komunikační způsob nebyl dostačující pro dnešní moderní aplikace. Byla potřeba pro komunikační kanál, který bude sloužit k obousměrné komunikaci.
HTTP by měl být víceméně bezstavový a klient a server mezi sebou komunikují jen když je třeba, jinak je spojení mezi nimi uzavřeno. Navíc, prohlížeč (klient) musí požádat server o komunikaci a server může na tuto žádost odpovědět. Ta žádost, to je ten http request. Jinak server neumí kontaktovat klienta jen tak sám od sebe.
U websocketů je tomu jinak. Jedná se o komunikační kanál, který se otevře jednou, na začátku a poté se používá ke komunikaci klienta a serveru v obou stranách. To znamená, že server může posílat data zároveň co klient posílá data na server. Toto se odborně jmenuje full-duplex.
Web socket má menší overheat přenosu dat, umí být real-time a hlavně, server může posílat data na klienta, aniž by si je klient musel explicitně vyžádat requestem. Toto je užitečné například u aplikací, které zobrazují real time data a server posílá tato data klientovi. Takže pokud nastane nějaká změna dat, server je prostě pošle na klienta.
Toto dříve nebylo možné provést pouze pomocí http protokolu.
Minimální příkladNajlepšie je vyskúšať si tieto koncepty v praxi. Dnes budeme pracovať s Flaskom, knižnicou SocketIO a javascript knižnicami socket.io a jQuery. Budem predpokladať, že Flask aplikácie aspoň trochu poznáš.
Začneme tým, že si vytvoríme nové virtuálne prostredie:
Nejlepší je vyzkoušet si tyto koncepty v praxi. Dnes budeme pracovat s Flaskem, knihovnou SocketIO a javascript knihovnami socket.io a jQuery. Budu předpokládat, že Flask aplikace alespoň trochu znáš.
Začneme tím, že si vytvoříme nové virtuální prostředí:
$ mkdir websockets_primer
$ cd websockets_primer
$ virtualenv venv
$ . venv/bin/activate
(venv) $Nainstalujeme závislosti, které budeme potřebovat:
(venv)$ pip install flask, flask-socketioV době psaní tohoto článku jsem používal verze Flask==1.0.2 a Flask-SocketIO=3.0.1.
Když už máme připravené prostředí a nainstalované závislosti, uděláme nový soubor server.py
from flask import Flask
from flask import render_template
from flask_socketio import SocketIO
app = Flask(__name__)
app.config["SECRET_KEY"] = "secret"
socketio = SocketIO(app)
@app.route("/")
def index():
return render_template("index.jinja")
@socketio.on("event")
def handle_event(data):
print(data)
if __name__ == '__main__':
socketio.run(app, debug=True)
Na začátku máme importy jako pro každou jinou Flask aplikaci, avšak přibylo nám tam from flask_socketio import SocketIO. Tento naimportovaný modul je v podstatě totéž jako jiné Flask rozšíření .
Inicializaci websocketů ve Flask aplikací provedeme pomocí řádku socketio = SocketIO(app). Pomocí tohoto objektu socketio budeme přijímat a odesílat zprávy.
Minimální aplikace by měla mít alespoň jednu stránku. V našem případě to bude index.jinja. Toto je třeba, protože musíme poskytnout i klientskou část naší aplikace. Tam bude javascript knihovna socketio a nějaké další funkce.
Websockety umí přijímat a posílat zprávy. Provedeme zatím jen přijímání zpráv. Pomocí řádku socketio.on("event")definuji handler pro událost event. V tomto případě jednoduše vypíšu data na konzoli.
@socketio.on("event")
def handle_event(data):
print(data)
Posílání a přijímání dat na obou stranách (klient a server) probíhá jako event. Toto je důležitý fakt, protože architektura aplikace založené na eventech ( event driven architecture ) funguje trošku jinak než klasické volání funkce. Neříkám, abys měl z toho paniku teď, ale měj to na paměti.
Pokud znáš Flask aplikace, tak spuštění appky vypadá většinou takto
if __name__ == "__main__":
app.run("0.0.0.0", debug=True)
My ale musíme appku spustit jinak, jelikož používáme websockety. Spustíme ji pomocí objektu socketio, který jsme si vytvořili na začátku.
if __name__ == '__main__':
socketio.run(app, debug=True)
Nyní musíme ještě vytvořit 2 soubory. Snažíme se renderovat index.jinja a také musíme vytvořit hlavní javascript soubor, do kterého budeme psát klientskou část naší websocketové ukázky.
Vytvořím složku templates a do ní soubor index.jinja
<!DOCTYPE HTML>
<html>
<head>
<title>Websockets test</title>
<script type="text/javascript" src="//code.jquery.com/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script>
<script type="text/javascript" src="{{ url_for("static", filename="js/main.js")}}"></script>
</head>
<body>
<form id="emit_event" method="post">
<input type="submit" value="emit">
</form>
</body>
</html>
Důležité jsou 3 importy v hlavičce html dokumentu. První importuje jQuery , druhý importuje knihovnu pro práci se sockety socketio a poslední import je pro náš main.js soubor, který musíme ještě vytvořit.
Jinak tento html dokument obsahuje pouze jeden formulář s jedním tlačítkem. To budeme používat k posílání zprávy přes websocket.
Vytvoříme složku static v ní js a v ní už konečně soubor main.js
Obsah bude vypadat asi takto:
$(document).ready(function() {
var url = location.protocol + "//" + document.domain + ":" + location.port
var socket = io.connect(url);
$("form#emit_event").submit(function(event) {
socket.emit("event", "test message");
return false;
});
});
Toto je hlavní logika klientské části. Z tadeto budeme přijímat a posílat zprávy přes websockety stejně jako na serverové části.
Pomocí řádku var socket = io.connect(url); se připojím na můj server. Následně pomocí jQuery upravím chování buttonu, aby při stisku poslal zprávu. K tomu slouží funkce socket.emit()
Okej, základ máme hotový a můžeme nyní zkoušet posílat zprávy. Aplikaci spustím pomocí příkazu:
(venv)$ python server.py
WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance.
* Serving Flask app "server" (lazy loading)
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance.
* Debugger is active!
* Debugger PIN: 478-618-530Otevřu prohlížeč na http://localhost:5000 a zobrazí se mi jeden button. Když ho zmáčknu na konzole mi vyskočí:
test messagePojďme tedy prozkoumat, jaké možnosti nám poskytuje tato knihovna socketio.
Přijímání zprávJak jsem již zmiňoval, přijímání zpráv na obou stranách probíhá jako event. V Pythonu musíme pro takovýto event definovat handler. V javascriptu používáme tvz. callbacky. V principu jde o totéž, ale každý jazyk má své vlastní technické řešení a my si toho musíme být vědomi.
Každý event, který chci přijmout musím mít nějaké jméno. V příkladu jsme měli název event. Mohu ale použít cokoli
@socketio.on("foobar")
def handle_data(data):
print(type(data))
print(data)Také data se automaticky mění na příslušný typ. Pokud v javascriptu pošlu string, tak string dostanu i na serveru. Totéž platí pro jiné datové typy
...
$("form#emit_event").submit(function(event) {
socket.emit("foobar", "message");
socket.emit("foobar", [1,2,3,]);
socket.emit("foobar", {data : "message"});
return false;
});
...Po odeslání událostí dostanu výpis na serveru
<class 'str'>
message
<class 'list'>
[1, 2, 3]
<class 'dict'>
{'data': 'message'}Handler také může mít několik argumentů
@socketio.on("sum")
def handle_sum(arg1, arg2):
print(arg1 + arg2)Upravíme javascriptovou část a zavoleme event s více argumenty
...
$("form#emit_event").submit(function(event) {
socket.emit("sum", 23, 47);
return false;
});
...Namespace patří mezi další funkce, které mám knihovna SocketIO nabízí. Každý event si můžeme rozdělit podle namespace. To nám dává další možnosti organizace eventov.
@socketio.on("sum", namespace="/math")
def handle_sum(arg1, arg2):
print(arg1 + arg2)Ovšem pozor! Na straně klienta se musíme nyní připojit na jinou url
$(document).ready(function() {
var namespace = "/math";
var url = location.protocol + "//" + document.domain + ":" + location.port;
var socket = io.connect(url + namespace);
$("form#emit_event").submit(function(event) {
socket.emit("sum", 23, 47);
return false;
});
});Další vychytávka je to, že každý event, který pošleme, umí zavolat callback poté, co byl proveden. Například z javascriptu pošlu nějaká data na server a server mi ještě dodatečně potvrdí, že data byla zpracována. Aha takhle
...
$("form#emit_event").submit(function(event) {
var ack = function(arg){console.log(arg)};
socket.emit("sum", 23, 47, ack);
return false;
});
...Pokud chci, aby se callback zavolal, musím v Pythonu vrátit nějakou hodnotu z provedeného handleru => return True
@socketio.on("sum", namespace="/math")
def handle_sum(arg1, arg2):
print(arg1 + arg2)
return TrueMusím si otevřít v prohlížeči konzoli (já používám chrome) a když zmáčknu tlačítko, dostanu výpis na konzoli[Image]
Posílání zprávZasílat eventy jsme již posílali, ale pouze z javascriptu. V Pythonu to vypadá velmi podobně. Používáme 2 funkce send a emit mezi nimiž je zásadní rozdíl.
Nejprve musíme importovat z knihovny flask-socketio
from flask_socketio import send
from flask_socketio import emitupravíme funkci na sčítání
@socketio.on("sum", namespace="/math")
def handle_sum(arg1, arg2):
value = arg1 + arg2
print("{} + {} = {}".format(arg1, arg2, value))
send(value)a přidáme handler v javascriptu abychom mohli tento event zachytit.
...
$("form#emit_event").submit(function(event) {
socket.emit("sum", 23, 47);
return false;
});
socket.on("message", function(data){
console.log("received message: " + data)
});
...Všimni si, že teď jsem použil handler, který zpracovává event s názvem message. Není to náhoda. Jde totiž o to, že funkce send posílá tvz. unnamed event . Tyto eventy se vždy posílají na handler, který zpracovává message.
Narozdíl od funkce send, funkce emit posílá již konkrétní event a musíš mu dát název. Zkusme tedy pozměnit náš příklad
@socketio.on("sum", namespace="/math")
def handle_sum(arg1, arg2):
value = arg1 + arg2
print("{} + {} = {}".format(arg1, arg2, value))
emit("result", value)...
socket.on("result", function(data){
console.log("sum is: " + data)
});
...BroadcastingVelmi užitečná funkce je broadcastování, což už z názvu vyplývá, že eventy se budou vysílat na všechny připojené klienty. Dejme tomu, že změníme funkci emit na broadcastování
@socketio.on("sum", namespace="/math")
def handle_sum(arg1, arg2):
value = arg1 + arg2
print("{} + {} = {}".format(arg1, arg2, value))
emit("result", value, broadcast=False)Nyní, když si otevřeš 2 prohlížeče a v jednom zmáčkneš button, výsledek součtu se ukáže ve všech prohlížečích[Image]
note: callbacky se při broadcastování nebudou provádět
ZávěrWebsockety mají mnoho využití. Tento článek byl jen úvod a přehled některých základních funkcí. V příštím blogu uděláme malou aplikaci postavenou na websocketech.
Máš nějaké dotazy k článku? Napiš ji do komentáře.
Nejčastější dotazy FAQ: WebSockety ve FlaskuCo je WebSocket?WebSocket je protokol umožňující obousměrnou komunikaci mezi klientem a serverem v reálném čase.
K čemu se používají WebSockety?Používají se například při chat aplikacích, live notifikacích, online hrách nebo real-time dashboardech.
Jak implementovat WebSockety ve Flasku?Nejčastěji se používá knihovna Flask-SocketIO, která umožňuje snadné vytvoření WebSocket serveru.
Je WebSocket rychlejší než HTTP?WebSocket má menší overhead a umožňuje trvalé spojení mezi klientem a serverem, což je efektivnější při real-time komunikaci.