Using the SSE protocol in Python Flask - javascript

I am trying to write a realtime web app which can respond real time messages updating in Flask framework. I am using code from http://flask.pocoo.org/snippets/116/ but JavaScript EventSource SSE not firing in browser. From the log I can see I've published the data successfully but the webpage(http:xxxx:5000/) does not get updated at all.
import gevent
from gevent.wsgi import WSGIServer
from gevent.queue import Queue
from flask import Flask, Response
import time
# SSE "protocol" is described here: http://mzl.la/UPFyxY
class ServerSentEvent(object):
def __init__(self, data):
self.data = data
self.event = None
self.id = None
self.desc_map = {
self.data : "data",
self.event : "event",
self.id : "id"
}
def encode(self):
if not self.data:
return ""
lines = ["%s: %s" % (v, k)
for k, v in self.desc_map.iteritems() if k]
return "%s\n\n" % "\n".join(lines)
app = Flask(__name__)
subscriptions = []
# Client code consumes like this.
#app.route("/")
def index():
debug_template = """
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1>Server sent events</h1>
<div id="event"></div>
<script type="text/javascript">
var eventOutputContainer = document.getElementById("event");
var evtSrc = new EventSource("/subscribe");
evtSrc.onmessage = function(e) {
console.log(e.data);
eventOutputContainer.innerHTML = e.data;
};
</script>
</body>
</html>
"""
return(debug_template)
#app.route("/debug")
def debug():
return "Currently %d subscriptions" % len(subscriptions)
#app.route("/publish")
def publish():
#Dummy data - pick up from request for real data
def notify():
msg = str(time.time())
for sub in subscriptions[:]:
sub.put(msg)
print 'data is ' + str(time.time())
gevent.spawn(notify)
return "OK"
#app.route("/subscribe")
def subscribe():
def gen():
q = Queue()
subscriptions.append(q)
try:
while True:
result = q.get()
ev = ServerSentEvent(str(result))
print 'str(result) is: ' + str(result)
yield ev.encode()
except GeneratorExit: # Or maybe use flask signals
subscriptions.remove(q)
return Response(gen(), mimetype="text/event-stream")
if __name__ == "__main__":
app.debug = True
server = WSGIServer(("", 5001), app)
server.serve_forever()
# Then visit http://localhost:5000 to subscribe
# and send messages by visiting http://localhost:5000/publish
Can you please shed some lights? I am testing with Chrome/34.0.1847.116. Thanks.

Related

websocket only sends one message?

I have a small server in Python that I'm trying to get to terminate based on a message it gets from a client. This is the server:
async def receiver(websocket, path):
received_data = await websocket.recv()
print("< {}".format(received_data))
if received_data == "Order66":
websocket.keep_running = False
asyncio.get_event_loop().stop()
else:
print("< {}".format(received_data))
def start_the_server():
start_server = websockets.serve(receiver, 'localhost', 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
def main():
start_the_server()
if __name__ == "__main__":
main()
The "client" is a larger app which also has a Javascript API and I am trying to this:
// send the info and disconnect
const socket2 = new WebSocket('ws://localhost:8765');
socket2.addEventListener('open', function (event) {
socket2.send(stats);
socket2.send("Order66");
});
However, it only ever sends "stats" (in this case it's a string), and the second send(...) never seems to get sent.

Telethon, global variable stops websocket

So, I am trying to use telethon,quart and websockets to create a dynamic page that only receives updates when the client receives a message event:
from telethon import TelegramClient, connection, events, utils
import hypercorn.asyncio
from quart import Quart, websocket
from variables import api_id, api_hash
from datetime import datetime
import asyncio
# Session name, API ID and hash to use; loaded from environmental variables
SESSION = 'quart';
# Telethon client
client = TelegramClient(SESSION, api_id, api_hash)
client.parse_mode = 'html'
###paginas html
teste_html = """
<!doctype html>
<html>
<head>
<title>Quart + Telethon</title>
<link rel="stylesheet" type="text/css" href="/static/style.css">
</head>
<body>
<h1>Página de teste!</h1>
<p>No momento, estamos testando o websocket</p>
<ul></ul>
<script type="text/javascript">
let socket = new WebSocket('ws://localhost:8000/random');
socket.onmessage = function(event) {
var messages_dom = document.getElementsByTagName('ul')[0];
var message_dom = document.createElement('li');
var cotent_dom = document.createTextNode('Received: ' + event.data);
message_dom.appendChild(cotent_dom);
messages_dom.appendChild(message_dom);
};
</script>
</body>
</html>
"""
###
app = Quart(__name__)
lock = asyncio.Lock();
#app.before_serving
async def startup():
await client.connect()
#app.after_serving
async def cleanup():
await client.disconnect()
#client.on(events.NewMessage)
async def receber(event):
global x;
try:
sender = await event.get_sender();
name = utils.get_display_name(sender);
message = name + "::::::" + event.text + "\n";
await lock.acquire();
x = message;
try:
file = open('msg.txt', '+a');
file.write(message);
file.close();
finally:
lock.release()
except(KeyboardInterrupt):
print("Adeus!");
except Exception as e:
print("\n======ERRO======\n");
print(e);
###websockets
#app.websocket('/random')
async def random():
global x;
import random
from time import sleep
try:
while True:
if x != 1:
print(x);
data = random.randint(0,5);
data = str(data);
await websocket.send(f"Quart enviou 1: {data}");
else:
besteira = 0;
sleep(1);
except Exception as e:
print(e);
print("erro 2");
#app.route('/')
async def hello_world():
await client.send_message('me', 'Hello World')
#return 'Message Sent!'
return teste_html;
async def main():
await hypercorn.asyncio.serve(app, hypercorn.Config())
if __name__ == '__main__':
x = 1;
try:
client.loop.run_until_complete(main())
except KeyboardInterrupt:
print("Adeus!");
except Exception as e:
print(e);
The idea is that, when a first new message is received, the websocket will start to send random numbers to the page (I am begginer), however, it just, locks, it not even writes on the msg.txt file, and here is the thin, this only happens if you use the if statement. If you take away the if statement, it runs, slow, but it runs, but if you put the if statement, it just locks.
If you can't help me, can you suggest me any quart/telethon/websocket tutorial? Or an example? If not,the is any other way to do this? Any better way to create a dynamic page?
Any help is very welcome.
The solution is that I am idiot, I was using synchronous sleep, I don't know exactly how this was breaking stuff, but it was, here is the fixed API:
#app.websocket('/random')
async def random():
global mensagem;
import random
#from time import sleep
mensagem_antiga = "";
try:
for i in range(1, 1000):
if mensagem_antiga != mensagem:
await websocket.send(f" {mensagem}");
mensagem_antiga = mensagem;
await asyncio.sleep(1);
except Exception as e:
print(e);
print("erro 2");

How to dynamically update context values in render_template() via GET requests?

I have a framework that retrieves the values of a slider via POST requests from HTML to Flask using AJAX. This calls the /about endpoint to perform some data manipulation and generates a redirected link defining the API url. Another endpoint (/index) then takes that API url and retrieves a response from the data source. However, when I attempt to render the template under the /index endpoint, I am unable to observe a dynamically rendering template suited for further upstream data visualization tasks.
Here's a very basic example illustrating the pipeline I've laid out:
./templates/index.html:
<body>
<div id="map" style="height: 700px;"></div>
<form method = 'POST'>
<div class="rangeslider">
<input style="width: 50%;" type="range" min="0" max="64" value="64" class="myslider" id="sliderRange">
<p>
<span id="demo"></span>
</p>
</div>
<h1><b>Testing context value: {{ asdf }}</b></h1>
<script>
const Http = new XMLHttpRequest();
var rangeslider = document.getElementById("sliderRange");
var output = document.getElementById("demo");
var current;
rangeslider.oninput = function() {
// Step 1: receives value from HTML slider object
current = this.value;
Http.open('POST', '/about')
Http.send(current)
}
</script>
</body>
./app.py:
from flask import Flask, render_template, request, redirect, url_for, jsonify
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
#app.route('/')
def main():
return redirect('/index')
#app.route('/index', methods=['GET'])
def index():
asdf = request.args.get('asdf')
// Step 3: asdf undergoes request to pull in appropriate data from data source and would ideally generate custom HTML prepped to inject a child into an existing leaflet.js app
// Issue occurs in asdf = asdf statement. Ideally asdf will represent custom HTML dependent on slider value.
return render_template('index.html', asdf = asdf)
#app.route('/about', methods=['POST'])
def about():
received_data = request.data
// Step 2: received_data undergoes further preprocessing to create API URL
return redirect(url_for("index", asdf=str(received_data)))
if __name__ == '__main__':
app.run(port=8000)
I know that AJAX is typically the best solution to deal with dynamic contexts, but I am unsure how to make the appropriate fixes to suit my overall needs in this case.
Any assistance would be greatly appreciated. Thanks!
This is how you can pass parameters to route:
from flask import Flask, render_template, request, redirect, url_for, jsonify
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
#app.route('/')
def main():
return redirect('/index')
#app.route('/index/<asdf>', methods=['GET'])
def index(asdf):
// Step 3: asdf undergoes request to pull in appropriate data from data source and would ideally generate custom HTML prepped to inject a child into an existing leaflet.js app
// Issue occurs in asdf = asdf statement. Ideally asdf will represent custom HTML dependent on slider value.
return render_template('index.html', asdf = asdf)
#app.route('/about', methods=['POST'])
def about():
received_data = request.data
// Step 2: received_data undergoes further preprocessing to create API URL
return redirect(url_for("index", asdf=str(received_data)))
if __name__ == '__main__':
app.run(port=8000)
Adding to optimising your code, you don't need to pass asdf to index and reload the html. You can pass asdf as a response from /about to your js and insert it into the html. This might help you.
In your app.py
#app.route('/about', methods=['POST'])
def about():
received_data = request.data
data = {'status':'success', 'xyz':received_data}
resp = make_response(jsonify(data), 200)
return resp
You may then access this response in the XMLHttp request as:
<script>
const Http = new XMLHttpRequest();
var rangeslider = document.getElementById("sliderRange");
var output = document.getElementById("demo");
var current;
rangeslider.oninput = function() {
// Step 1: receives value from HTML slider object
current = this.value;
Http.open('POST', '/about')
Http.send(current)
}
Http.onload = function() {
var response = JSON.parse(Http.responseText)
if (response['status'] == "success") {
$('#tag').html(response['xyz'])
}
}
</script>
You may change how you use the response to your context.

Small File Upload with Flask is Slow

I'm uploading small files (sub 20k) using Fetch and Flask, however it can take up to 10 seconds to post, process and then return. Conversely, if i run the same functions in pure python only the same load and process time is less than a second.
When running a file upload with no processing it is almost instant upload.
Am I missing something that slows things down when Im processing files in with Flask?
I've tried uploading with no processing (fast), I've tried processsing with no flask (fast). I've tried uplaoding and processing from browser with flask(slow)
from flask import render_template, url_for, flash, redirect, request, jsonify, Response, Flask, session, make_response, Markup
import io, random, sys, os, pandas
app = Flask(__name__)
#app.route("/")
############################################################
#app.route("/routee", methods = ['GET', 'POST'])
def routee():
return render_template('Upload Test.html')
############################################################
#app.route("/routee/appendroute", methods = ['GET', 'POST'])
def appendroute():
PrintFlask(request.files.getlist('route'))
return make_response(jsonify('Voyage = VoyageJson, Intersects = IntersectJson'), 200)
############################################################
if __name__ == "__main__":
app.run(debug=True)
<script type="text/javascript">
function prepformdata(route){
formdata = new FormData();
for (var i = 0; i < route.files.length; i++) {
formdata.append('route', route.files[i]);
}
return formdata
}
//////////////////////////////////////////////////////////////
function appendroutes(formdata, route) {
uploadfiles = document.getElementById("files")
formdata = prepformdata(uploadfiles)
InitConst ={method: "POST",body: formdata}
url = window.origin + '/routee/appendroute'
fetch(url,InitConst)
.then(res => res.json())
.then(data => {addon = data['Voyage']; addonIntersects = data['Intersects']})
.then(() => console.log('Route(s) Added'))
}
</script>
Whilst the code above is very nippy. I'm expecting to do some equally nippy processing server side. But something is slowing it down. Any ideas why processing might slow down when flask is used?

Tornado- how to call a handler in another function

I am currently trying to use Tornado's web-socket handlers to update a dashboard every time a certain function is called. here is the handler:
class WebSocketHandler(websocket.WebSocketHandler):
clients = []
def open(self):
logging.info("WEBSOCKET OPEN")
WebSocketHandler.clients.append(self)
def on_message(self, message):
logging.info("message from websocket recieved")
self.write_message("WebSocket connected")
def on_close(self):
logging.info("WEBSOCKET closed")
and here is the client-side script which connects the WebSocket on load:
function WebSocketTest()
{ var ws = 0;
ws = new WebSocket("ws://localhost:8008/WEB");
ws.onopen = function()
{
ws.send("initial connect")
}
ws.onmessage = function (evt)
{
console.log(evt.data)
};
ws.onclose = function()
{
console.log("closed ");
};
}
The websockets connect sucessfully.
i need to call write_message from WebSocketHandler but i'm very confused what the instance of it is? the error i keep running into is that self isn't defined but im not sure what self is exactly? i know that WebSocketHandler gets run whenever the client tries to load ^/WEB$
EDIT: here is my server.py file, i need to call write_message right after the spawn callback call in itercheckers
class Server():
#classmethod
def run(cls):
options.parse_command_line()
# Start web
template_path = os.path.join(os.path.dirname(__file__), 'templates')
jinja2loader = Jinja2Loader(template_path)
kwargs = dict(
template_loader=jinja2loader,
static_path=os.path.join(os.path.dirname(__file__), 'static'),
debug=True,
login_url="/auth/login",
cookie_secret="dn470h8yedWF9j61BJH2aY701i6UUexx"
)
app = web.Application(handlers, **kwargs).listen(
configuration['server']['port'],)
# Reset events
#gen.coroutine
def reset(parent=None):
if parent is None:
parent = configuration
# Reset event happyness
yield events.reset_happy(parent)
# Read last status
data = yield events.get(parent)
# Read and set happy from the last status
happy = (data or {}).get('status', events.STATUS_OK) \
in events.HAPPY
yield events.set_happy(parent, happy)
# Iterate sub-events
for event in parent['events']:
yield reset(event)
ioloop.IOLoop.current().run_sync(reset)
# Start checkers
def itercheckers(parent):
index = 0
for event in parent.get('events', []):
if 'checker' in event:
checker = event['checker']
p, m = checker['class'].rsplit('.', 1)
ioloop.IOLoop.current().spawn_callback(
getattr(importlib.import_module(p), m)(
event=event,
frequency=checker.get('frequency', 1),
params=checker['params']
).run)
index += 1
itercheckers(event)
itercheckers(configuration)
# Start alerts
ioloop.IOLoop.current().run_sync(alerts.reset)
for alert in configuration['alerts']:
p, m = alert['class'].rsplit('.', 1)
ioloop.IOLoop.current().spawn_callback(
getattr(importlib.import_module(p), m)(
alert=alert
).run
)
# Start loop
ioloop.IOLoop.current().start()
First thing first, self keyword is pointed to current websocket client that is handled in the moment. To use tornado websockets you must initialize tornado app
app = web.Application([
(r'/ws', WSHandler), #tells it to redirect ws:// to websocket handler
#Choose different names from defaults because of clarity
])
if __name__ == '__main__':
app.listen(5000) #listen on what port
ioloop.IOLoop.instance().start()
Then you must have WSHandler class you're pointing websockets trafic to
class WSHandler(websocket.WebSocketHandler):
#crossdomain connections allowed
def check_origin(self, origin):
return True
#when websocket connection is opened
def open(self):
print("Client connected ")
def on_close(self):
print("Client disconnected")
def on_message(self,message):
self.write_message(message) #echo back whatever client sent you
So complete app would look like
from tornado import websocket, web, ioloop
clients = []
#whenever you want to broadcast to all connected call this function
def broadcast_message(msg):
global clients
for client in clients:
client.write_message(msg)
class WSHandler(websocket.WebSocketHandler):
#crossdomain connections allowed
def check_origin(self, origin):
return True
#when websocket connection is opened
def open(self):
#here you can add clients to your client list if you want
clients.append(self)
print("Client connected ")
def on_close(self):
clients.remove(self)
print("Client disconnected")
def on_message(self,message):
self.write_message(message) #echo back whatever client sent you
app = web.Application([
(r'/ws', WSHandler), #tells it to redirect ws:// to websocket handler
#Choose different names from defaults because of clarity
])
if __name__ == '__main__':
app.listen(5000) #listen on what port
ioloop.IOLoop.instance().start()
And now to connect to it with js
function WebSocketTest()
{
var ws = new WebSocket("ws://localhost:5000/ws");
ws.onopen = function()
{
ws.send("initial connect")
}
ws.onmessage = function (evt)
{
console.log(evt.data)
};
ws.onclose = function()
{
console.log("closed ");
};
}
I haven't tested it but should work

Categories