Test case implemented with Python and CherryPy:
import cherrypy, time
class Root():
#cherrypy.expose
def index(self):
return r'''<!DOCTYPE html>
<html>
<head>
<title>Server-sent events test</title>
<style>html,body,#test{height:98%;}</style>
</head>
<body>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
var source = new EventSource('gettime');
source.addEventListener('time', function (event) {
document.getElementById('test').innerHTML += event.data + "\n";
});
source.addEventListener('error', function (event){
console.log('SSE error:', event);
console.log('SSE state:', source.readyState);
});
}, false);
</script>
<textarea id="test"></textarea>
</body>
</html>'''
#cherrypy.expose
def gettime(self):
cherrypy.response.headers["Content-Type"] = "text/event-stream"
def generator():
while True:
time.sleep(1)
yield "event: time\n" + "data: " + str(time.time()) + "\n\n"
return generator()
gettime._cp_config = {'response.stream': True}
if __name__ == '__main__':
cherrypy.config.update({'server.socket_host': '0.0.0.0'})
cherrypy.quickstart(Root())
After receiving some messages successfully I manually drop the connection, then in Firefox' web console appears JS Error: The connection to http://localhost:8080/gettime was interrupted while the page was loading.
According to the spec, Clients will reconnect if the connection is closed, but Firefox doesn't. Error event handler reports that the source is in CLOSED state.
CLOSED (numeric value 2)
The connection is not open, and the user agent is not trying to reconnect. Either there was a fatal error or the close() method was invoked.
So there was a fatal error?
In Chromium it works, error handler reports that the source is in CONNECTING (0) state (as it should) and connection is automatically restored within a few seconds
Have tried Firefox 26, Firefox 24 ESR and Iceweasel 17 on Linux and Windows platforms, all the same
Have checked raw protocol and headers, looks ok
Have tried to add retry: 3000 to each sent event
Have tried to move JavaScript out of event listener and wrapping it into setTimeout
The bug is fixed in Firefox 36.
The spec on this stuff is in flux and there is a number of open spec issues related to the reconnection behavior that the spec proposes. I wouldn't rely on any specific reconnection behavior until the spec stabilizes a lot more than it has so far.
Related
I have a flask app that is running selenium on a VM server. I am running a long looping function, and I want to alert the user on the client side if the driver have reached certain points while scrawling (because the function runs in a very long loop, it's not possible afaik to use flask flash without returning redirect or render_template)
This why I think socketio is my only option.
What I have currently is:
Snippet from routes.py (trying to connect a driver)
#app.route("/", methods=["GET", "POST"])
#login_required
def index():
if request.method == "POST":
try:
globals()["driver_" + str(user_id)] = uc.Chrome(options=chrome_options, use_subprocess=True)
What I want to achieve:
try:
globals()["driver_" + str(user_id)] = uc.Chrome(options=chrome_options, use_subprocess=True)
message = "Driver connected succssefully"
socket.emit('send-message', message)
I have the following in layout.html:
<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 type="text/javascript" charset="utf-8">
var socket = io();
socket.on('connect', function() {
console.log('initSocketIO');
});
socket.on('send-message', (message) => {
console.log(message);
});
</script>
I can see in the console initSocketIO but how do I set it up that I will also see send-message data ("Driver connected succssefully") on the console?
I've been trying to get this flask server to update itself with data generated from a loop that runs on a a .py script when called for by the user via push button on webpage. I've been looking into recommended solutions and have seen websockets (sockets.io), ajax, nodejs come up. I understand that i need to implement some form of js in my project, and ajax looked to be the most simple (so i thought). I only have about 3 weeks of experience programming in python. Mainly i look for examples close to what i want, and then try to modify it to suit my needs, but haven't found any examples for what i'm looking for. Even then, my general newness to programming means that the more examples i "tack on" the more likely i am to degrade the overall structure of what i've already accomplished.
Goal
The goal is to update a value displayed on the page without a reload but instead have js update the value every second. The value is generated from a x=x+1 counter in my .py file. This will be replaced by sensor inputs gathered from my Rpi later.
Actual results
When i run the current code,
my html elements get double posted so i see what i've put into the index.html file twice although the second button elements don't actually respond to clicking,
I also get an endless stream of Posts in my terminal window.
Clicking on the button elements no longer execute my loop in the .py file and instead displays "Method not allowed"
What i've tried
I've tried to implement setTmeout in my html file as a way to call back to the python app and get an updated value (the x=x+1) every second. I've read posts around using setTimeout as a way to deal with issues using setInterval. Due to the variety of ways i've seen ajax calls employed and learning resources being primarily structured towards forms, databases, and chat apps, most of my searches aren't bringing up anything new for me to learn from that might help. I'm currently doing ajax tutorials hoping to come accross something i can use, any help would be greatly appreciated.
ajaxTest.py My python flask file
import threading
import time
from flask import Flask, render_template, jsonify, request
import RPi.GPIO as GPIO
import datetime
from datetime import datetime
from datetime import timedelta
app = Flask(__name__)
bioR_on = False
ledGrnSts = 0
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
air = 21
light = 20
waste = 16
feed = 12
water = 26
pinList = [21,20,16,12,26]
def pump(pin):
GPIO.output(pin, GPIO.LOW)
print(pin,'on')
time.sleep(1)
GPIO.output(pin, GPIO.HIGH)
print(pin, 'off')
time.sleep(1)
def on(pin):
GPIO.output(pin, GPIO.LOW)
#app.route("/")
def index():
templateData = {
'title' : 'Bioreactor output Status!',
'ledGrn' : ledGrnSts,
}
return render_template('index.html', **templateData)
#app.route('/<deviceName>/<action>', methods = ["POST"])
def start(deviceName, action):
# script for Pi Relays
def run():
if action == "on":
alarm = datetime.now() + timedelta(seconds =10)
global bioR_on
bioR_on = True
while bioR_on:
tday = datetime.now()
time.sleep(1)
#feed(tday, alarm)
x=x+1
return jsonify(x)
GPIO.setmode(GPIO.BCM)
for i in pinList:
GPIO.setup (i, GPIO.OUT)
GPIO.output(i, GPIO.HIGH)
on(air)
on(light)
print(tday)
if tday >= alarm:
print('alarm activated')
# run = False
pump(waste)
print('waste activated')
pump(feed)
print('feed on')
GPIO.cleanup()
alarm = datetime.now() + timedelta(seconds =10)
print('next feeding time ', alarm)
time.sleep(1)
if action == 'off':
bioR_on = False
#return "off"
GPIO.cleanup()
thread = threading.Thread(target=run)
thread.start()
templateData = {
'ledGrn' : ledGrnSts,
}
return render_template('index.html', **templateData)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80, debug=True, threaded=True)
My index.html file
<!DOCTYPE html>
<head>
<title>BioReactor Control</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href='../static/style.css'/>
</head>
<body>
<h1>Actuators</h1>
<h2> Status </h2>
<h3> GRN LED ==> {{ ledGrn }}</h3>
<br>
<h2> Commands </h2>
<h3>
Activate Bioreactor Ctrl ==>
TURN ON
TURN OFF
</h3>
<h3>
Current Count
</h3>
<p id="demo"></p>
<script>
setTimeout($.ajax({
url: '/<deviceName>/<action>',
type: 'POST',
success: function(response) {
console.log(response);
$("#num").html(response);
},
error: function(error) {
console.log(error);
}
}), 1000);
</script>
<h1>Output</h1>
<h1 id="num"></h1>
</body>
</html>
Picture of results
I created minimal code which uses AJAX to get new value every 1 second.
I use setInterval to repeate it every 1 second. I also uses function(){ $.ajax ... } to create function which is not executed at once but setInterval will call it every 1 second. Without function(){...} code $.ajax was executed at start and its result was used as function executed every 1 second - but it returns nothing - so finally it updated value only once (at start) and later setInterval was running nothing.
I added current time to see if it is still running.
buttons run function '/<device>/<action>' which start and stop thread but AJAX uses /update to get current value.
I use render_template_string to have all code in one file - so other people can easily copy and test it.
I reduced HTML to minimal. To make sure I put <h1> before script which needs these tags.
I didn't tested it with global=True which may run it in new threads and it can make problem.
from flask import Flask, request, render_template_string, jsonify
import datetime
import time
import threading
app = Flask(__name__)
running = False # to control loop in thread
value = 0
def rpi_function():
global value
print('start of thread')
while running: # global variable to stop loop
value += 1
time.sleep(1)
print('stop of thread')
#app.route('/')
#app.route('/<device>/<action>')
def index(device=None, action=None):
global running
global value
if device:
if action == 'on':
if not running:
print('start')
running = True
threading.Thread(target=rpi_function).start()
else:
print('already running')
elif action == 'off':
if running:
print('stop')
running = False # it should stop thread
else:
print('not running')
return render_template_string('''<!DOCTYPE html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
TURN ON
TURN OFF
<h1 id="num"></h1>
<h1 id="time"></h1>
<script>
setInterval(function(){$.ajax({
url: '/update',
type: 'POST',
success: function(response) {
console.log(response);
$("#num").html(response["value"]);
$("#time").html(response["time"]);
},
error: function(error) {
console.log(error);
}
})}, 1000);
</script>
</body>
</html>
''')
#app.route('/update', methods=['POST'])
def update():
return jsonify({
'value': value,
'time': datetime.datetime.now().strftime("%H:%M:%S"),
})
app.run() #debug=True
I'm trying to get Server Sent Events to work from Python, so I found a little demo code and to my surprise, it only partly works and I can't figure out why. I got the code from here and put in just a couple little changes so I could see what was working (I included a print statement, an import statement which they clearly forgot, and cleaned up their HTML to something I could read a little easier). It now looks like this:
# Bottle requires gevent.monkey.patch_all() even if you don't like it.
from gevent import monkey; monkey.patch_all()
from gevent import sleep
from bottle import get, post, request, response
from bottle import GeventServer, run
import time
sse_test_page = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js "></script>
<script>
var es = new EventSource("/stream");
es.onmessage = function(e) {
document.getElementById("log").innerHTML = e.data;
}
</script>
</head>
<body>
<h1>Server Sent Events Demo</h1>
<p id="log">Response Area</p>
</body>
</html>
"""
#get('/')
def index():
return sse_test_page
#get('/stream')
def stream():
# "Using server-sent events"
# https://developer.mozilla.org/en-US/docs/Server-sent_events/Using_server-sent_events
# "Stream updates with server-sent events"
# http://www.html5rocks.com/en/tutorials/eventsource/basics/
response.content_type = 'text/event-stream'
response.cache_control = 'no-cache'
# Set client-side auto-reconnect timeout, ms.
yield 'retry: 100\n\n'
n = 1
# Keep connection alive no more then... (s)
end = time.time() + 60
while time.time() < end:
yield 'data: %i\n\n' % n
print n
n += 1
sleep(1)
if __name__ == '__main__':
run(server=GeventServer, port = 21000)
So here's what ends up happening: I can see the original header and paragraph on the website, but response area never changes. On the python side, it prints n once per second, but I never see that change on the web page. I get the feeling that I just lack a fundamental understanding of what I'm trying to do but I can't find anything missing.
I'm running Python 2.7, windows 7, chrome 43.0.2357.81 m.
EDIT: I got rid of the extra quotation mark. Now it only seems to update when it gets to 60 (which I guess is better than not at all...)
Why would it wait until the end of the function to send the event?
You've got 2 sets of quotes after p id="log""
Im pretty new to Autobahn and WAMP (Web Apps Messaging Protocol).
Im just creating a simple Application Component based on http://autobahn.ws/python/wamp/programming.html and https://github.com/crossbario/crossbarexamples/blob/master/votes/python/votes.py
Below is my Server side Python
from autobahn.asyncio.wamp import (
ApplicationSession,
ApplicationRunner
)
from autobahn import wamp
from asyncio import coroutine
class MyComponent(ApplicationSession):
#wamp.register("com.myapp.add2")
def add2(self, x, y):
print("added 2")
return x + y
#wamp.register("com.myapp.add3")
def add3(self, x, y, z):
print("added 3")
return x + y + z
#coroutine
def onJoin(self, details):
res = yield from self.register(self)
print("{} procedures registered.".format(len(res)))
if __name__ == '__main__':
runner = ApplicationRunner(url="ws://localhost:8080/ws", realm="realm1")
runner.run(MyComponent)
and the Client side
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script>AUTOBAHN_DEBUG = false;</script>
<script src="http://autobahn.s3.amazonaws.com/autobahnjs/latest/autobahn.min.jgz"></script>
<script>
var connection = new autobahn.Connection({
url: "ws://localhost:8080/ws",
realm: "realm1"
});
connection.onopen = function (session, details) {
session.call("com.myapp.add2", [2,3]).then(session.log);
session.call("com.myapp.add3", [2,3,4]).then(session.log);
};
connection.onclose = function (reason, details) {
console.log("Connection lost: " + reason);
};
connection.open();
</script>
</body>
</html>
Error
Looks like this is similar to https://github.com/hwmrocker/hextest/issues/2 but I can't get my head around. I can't even find a sample that works. This one (https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/wamplet/wamplet1) is similar but it has the same issue as well.
Surprisingly, when I run an external Crossbar sample on the same Port and run the above example, it works like a magic and I can see the results on the console.
I found this one (https://github.com/tavendo/AutobahnPython/blob/master/examples/asyncio/wamp/basic/server.py) but it looks quite complicated.
Please help me out.
Thank you in Advanced.
Your code works for me without modification:
Your app consists of 2 WAMP application components: a browser side (using AutobahnJS), and a server side (using AutobahnPython/Python3/asyncio).
For these 2 components to talk to each other, both components need to connect to a WAMP router. I used Crossbar.io.
Note that your Python component is logically a server-side component, but it is not technically a server: it does not open a listening port or what, but it connects to a WAMP router.
I am trying to get (the latest) Web.py and AJAX to play nice with each other, but so far I haven't had much luck.
Long story short, I am running both the server-side (Web.py) and the client side (Javascript) on my local development computer, but somehow all my AJAX GET requests are showing up as OPTION requests. From what I've read, this is typical is cases of cross domain requests, but since I'm running this on localhost I am not sure what's going on.
Here's the server-side code:
import web
import json
def make_text(string):
return string
urls = ('/', 'mainScreen',
'/update', 'update'
)
app = web.application(urls, globals())
global content
content = {'key1': 'value1', 'key2': 'value2'}
def getPayload():
return content
class mainScreen:
def GET(self):
web.header('Content-Type', 'application/json')
web.header('Access-Control-Allow-Origin', '*')
web.header('Access-Control-Allow-Credentials', 'true')
return getPayload()
def OPTIONS(self):
web.header('Content-Type', 'application/json')
web.header('Access-Control-Allow-Origin', '*')
web.header('Access-Control-Allow-Credentials', 'true')
return getPayload()
class update:
def POST(self):
global content
content = web.input(_method='post')
return "DONE."
if __name__ == '__main__':
app.run()
Here's the client-side code:
<html>
<head>
<title>WTF</title>
<script type="text/javascript" src="../static/jquery.js"></script>
<script type="text/javascript">
function dial()
{
console.log("Fire in the hole!");
$.ajax({
url: 'http://0.0.0.0:8080',
contentType: 'application/jsonp',
timeout : 5000,
cache: false,
success: function (data) {
console.log('[ajax] Connection successful! ' + JSON.stringify(data));
},
error:function (jqXHR, textStatus, errorThrown){
console.log(JSON.stringify(jqXHR) + ' ' + textStatus +' '+errorThrown );
}
});
console.log("Done.");
}
$(function() {
dial();
});
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>
This is Firebug's output:
Fire in the hole! index.html (line 9) DONE.
index.html (line 24) [ajax] Connection successful! ""
index.html (line 17)
Notice that the "" indicate that the request got empty data.
This is what Firebug's network panel shows:
If i open the page that Firebug indicates the data's there alright but if I, quite simply open http://0.0.0.0:8080/ on any browser, the data is displayed as expected! What is happening here?
Finally, here's Web.py's log:
hal#ubuntu:~/Desktop/tut$ python app.py
http://0.0.0.0:8080/
127.0.0.1:43796 - - [26/Jul/2013 11:14:59] "HTTP/1.1 OPTIONS /" - 200 OK
I'm coding in Ubuntu 12.04 LTS by the way.
PS: I also tried changing the response header inside Web.py to:
web.header('Content-Type', 'text/plain')
but it didn't work.
PS2: Changing the server address on the client-side script to "127.0.0.1:8080" or "localhost:8080" didn't help either.
Nailed it.
The issue was on the client-side code. I remove the contentType from the request itself and it worked perfectly.