Emit/Broadcast Messages on REST Call in Python With Flask and Socket.IO - python-2.7

Background
The purpose of this project is to create a SMS based kill switch for a program I have running locally. The plan is to create web socket connection between the local program and an app hosted on Heroku. Using Twilio, receiving and SMS will trigger a POST request to this app. If it comes from a number on my whitelist, the application should send a command to the local program to shut down.
Problem
What can I do to find a reference to the namespace so that I can broadcast a message to all connected clients from a POST request?
Right now I am simply creating a new web socket client, connecting it and sending the message, because I can't seem to figure out how to get access to the namespace object in a way that I can call an emit or broadcast.
Server Code
from gevent import monkey
from flask import Flask, Response, render_template, request
from socketio import socketio_manage
from socketio.namespace import BaseNamespace
from socketio.mixins import BroadcastMixin
from time import time
import twilio.twiml
from socketIO_client import SocketIO #only necessary because of the hack solution
import socketIO_client
monkey.patch_all()
application = Flask(__name__)
application.debug = True
application.config['PORT'] = 5000
# White list
callers = {
"+15555555555": "John Smith"
}
# Part of 'hack' solution
stop_namespace = None
socketIO = None
# Part of 'hack' solution
def on_connect(*args):
global stop_namespace
stop_namespace = socketIO.define(StopNamespace, '/chat')
# Part of 'hack' solution
class StopNamespace(socketIO_client.BaseNamespace):
def on_connect(self):
self.emit("join", 'server#email.com')
print '[Connected]'
class ChatNamespace(BaseNamespace, BroadcastMixin):
stats = {
"people" : []
}
def initialize(self):
self.logger = application.logger
self.log("Socketio session started")
def log(self, message):
self.logger.info("[{0}] {1}".format(self.socket.sessid, message))
def report_stats(self):
self.broadcast_event("stats",self.stats)
def recv_connect(self):
self.log("New connection")
def recv_disconnect(self):
self.log("Client disconnected")
if self.session.has_key("email"):
email = self.session['email']
self.broadcast_event_not_me("debug", "%s left" % email)
self.stats["people"] = filter(lambda e : e != email, self.stats["people"])
self.report_stats()
def on_join(self, email):
self.log("%s joined chat" % email)
self.session['email'] = email
if not email in self.stats["people"]:
self.stats["people"].append(email)
self.report_stats()
return True, email
def on_message(self, message):
message_data = {
"sender" : self.session["email"],
"content" : message,
"sent" : time()*1000 #ms
}
self.broadcast_event_not_me("message",{ "sender" : self.session["email"], "content" : message})
return True, message_data
#application.route('/stop', methods=['GET', 'POST'])
def stop():
'''Right here SHOULD simply be Namespace.broadcast("stop") or something.'''
global socketIO
if socketIO == None or not socketIO.connected:
socketIO = SocketIO('http://0.0.0.0:5000')
socketIO.on('connect', on_connect)
global stop_namespace
if stop_namespace == None:
stop_namespace = socketIO.define(StopNamespace, '/chat')
stop_namespace.emit("join", 'server#bayhill.com')
stop_namespace.emit('message', 'STOP')
return "Stop being processed."
#application.route('/', methods=['GET'])
def landing():
return "This is Stop App"
#application.route('/socket.io/<path:remaining>')
def socketio(remaining):
try:
socketio_manage(request.environ, {'/chat': ChatNamespace}, request)
except:
application.logger.error("Exception while handling socketio connection",
exc_info=True)
return Response()
I borrowed code heavily from this project chatzilla which is admittedly pretty different because I am not really working with a browser.
Perhaps Socketio was a bad choice for web sockets and I should have used Tornado, but this seemed like it would work well and this set up helped me easily separate the REST and web socket pieces

I just use Flask-SocketIO for that.
from gevent import monkey
monkey.patch_all()
from flask import Flask
from flask.ext.socketio import SocketIO
app = Flask(__name__)
socketio = SocketIO(app)
#app.route('/trigger')
def trigger():
socketio.emit('response',
{'data': 'someone triggered me'},
namespace='/global')
return 'message sent via websocket'
if __name__ == '__main__':
socketio.run(app)

Related

Flask SocketI) need to detect duplicate connections on opening new tab

I need to detect the duplicate session when user opens a new tab. Since I am using it for survey, I am not having any of the user's data. I am working on anonymous users.
Reading through documentation and various other thread I understood that I need to send client a session data which will be a uuid and check if the user is already authenticated for new connection.
My code is below -
from flask import Flask, render_template, session
from flask_session import Session
from flask_socketio import SocketIO, send, emit
from flask_login import LoginManager, UserMixin, current_user, login_user, logout_user, AnonymousUserMixin
import time, json, uuid, os
app = Flask(__name__)
app.config['SECRET_KEY'] = 'top-secret!'
app.config['SESSION_TYPE'] = 'filesystem'
login_manager = LoginManager(app)
login_manager.init_app(app)
Session(app)
socketio = SocketIO(app, cors_allowed_origins="*", logger=True, manage_session=False)
class User(UserMixin, object):
def __init__(self, id=None):
self.id = id
#login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
time_now = 0
msg = "Hello User. Please wait other users to join. Survey will start once minimum users will join. Max waiting time " \
"is 5 min "
# connected_msg_json = json.dumps(connected_msg, indent=4)
client_count = 0
#socketio.on('message')
def handle_message(msg):
print("Connected with the client with data " + msg)
#socketio.on('connect')
def test_connect():
print("Connected")
f = open('data.json')
data = json.load(f)
minUserCount = data['minimumNoOfUser']
global client_count, time_now
if current_user.is_authenticated:
pass
else:
client_count += 1
login_user(User(id=uuid.uuid1()))
if client_count == 0:
time_now = int(time.time())
print("Total no of connected client " + str(client_count))
print("About to send the time when first user connected " + str(time_now))
send(time_now)
if client_count > minUserCount:
send("Continue", broadcast=True)
#socketio.on('disconnect')
def test_disconnect():
print('Client disconnected')
logout_user()
global client_count
client_count -= 1
print("Total no of connected client " + str(client_count))
Since I need to make sure that survey opens when there are minimum no of unique users, I decided to login the users upon connection. And if the user is already authenticated then I believe it means it's the new connection via tab.
Now I am not sure if my code is okay but the packages are incorrect or both. I have tried to resolved the error but I am stuck with this error -
ImportError: cannot import name 'ContextVar' from 'werkzeug.local' (/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/werkzeug/local.py)
This appears when I put line
app.config['SESSION_TYPE'] = 'filesystem'
else I get different error about secret key not being set.
My requirements.txt are:
Flask==2.0.2
Flask-Cors==3.0.10
Flask-SocketIO==4.3.1
gevent==21.8.0
gevent-websocket==0.10.1
greenlet==1.1.2
gunicorn==20.1.0
python-engineio==3.13.2
python-socketio==4.6.0
simple-websocket==0.5.0
websocket-client==1.2.1
websockets==10.1
Werkzeug==0.14.1
you need to update werkzeug to 2.0 +

Tornado on pika consumer can't run

I want to build monitoring system using RabbitMQ and Tornado. I can run the producer and my consumer can consume the data on queue but the data cant be show on website.
This just my experiment before I using the sensor
import pika
import tornado.ioloop
import tornado.web
import tornado.websocket
import logging
from threading import Thread
logging.basicConfig(lvl=logging.INFO)
clients=[]
credentials = pika.credentials.PlainCredentials('ayub','ayub')
connection = pika.BlockingConnection(pika.ConnectionParameters('192.168.43.101',
5672,
'/',
credentials))
channel = connection.channel()
def threaded_rmq():
channel.basic_consume('Queue',
on_message_callback= consumer_callback,
auto_ack=True,
exclusive=False,
consumer_tag=None,
arguments=None)
channel.start_consuming()
def disconect_rmq():
channel.stop_consuming()
Connection.close()
logging.info('Disconnected from broker')
def consumer_callback(ch,method,properties,body):
for itm in clients:
itm.write_message(body)
class SocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
logging.info('websocket open')
clients.remove(self)
def close(self):
logging.info('websocket closed')
clients.remove(self)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("websocket.html")
application = tornado.web.Application([
(r'/ws',SocketHandler),
(r"/", MainHandler),
])
def startTornado():
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
def stopTornado():
tornado.ioloop.IOLoop.instance().stop()
if __name__ == "__main__":
logging.info('starting thread RMQ')
threadRMQ = Thread(target=threaded_rmq)
threadRMQ.start()
logging.info('starting thread tornado')
threadTornado = Thread(target=startTornado)
threadTornado.start()
try:
raw_input("server ready")
except SyntaxError:
pass
try:
logging.info('disconnected')
disconnect_rmq()
except Exception, e:
pass
stopTornado()
but I got this error
WARNING:tornado.access:404 GET /favicon.ico (192.168.43.10) 0.98ms
please help me
In your SocketHandler.open function you need to add the client not remove it.
Also consider using a set for clients instead of a list because the remove operation will be faster:
clients = set()
...
class SocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
logging.info('websocket open')
clients.add(self)
def close(self):
logging.info('websocket closed')
clients.remove(self)
The message you get regarding favicon.ico is actually a warning and it's harmless (the browser is requesting an icon to show for web application but won't complain if none is available).
You might also run into threading issues because Tornado and Pika are running in different threads so you will have to synchronize them; you can use Tornado's IOLoop.add_callback method for that.

How to send socket messages via Django views when socket server and views.py are split into two files?

Env: Python 3.6, and Django 2.1
I have created a Django website and a socket server, and files are organized like this:
web
...
user (a Django app)
__init__.py
views.py
...
server.py
Actually I want to build a umbrella rental system by using django, and server connects to umbrella shelf via multi-thread socket (sending some messages). Like I press the borrow button, and views.py can call the server test_function and send some messages to the connected umbrella shelf.
I can import server variables or functions in views.py, but I cannot get the right answer while server.py is running. So I want to ask you if you could give me some advice. Thanks a lot!
By the way, I tried to import the global variable clients directly in views.py, but still got [].
server.py defines a multi-thread server, which is basically as below:
clients = []
class StuckThread(threading.Thread):
def __init__(self, **kwargs):
self.name = kwargs.get('name', '')
def run(self):
while True:
# do something
def func1(self):
# do something
def test_function(thread_name):
# if the function is called by `views.py`, then `clients = []` and return 'nothing', but if I call this function in `server.py`, then I can get a wanted result, which is `got the thread`
for client in clients:
if client['thread'].name == thread_name:
return 'got the thread'
return 'nothing'
if __name__ == '__main__':
ip_port = ('0.0.0.0', 65432)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(ip_port)
server.listen(max_listen_num)
while True:
client, address = socket.accept()
param = {'name': 'test name'}
stuck_thread = StuckThread(**param)
clients.append({"client": client, "address": address, "thread": stuck_thread})
stuck_thread.start()
and I have a Django views.py like this
def view_function(request):
from server import clients
print(clients) # got []
form server import test_function
print(test_function('test name')) # got 'nothing'
return render(request, 'something.html')
I have solve this problem by socket communication between django views.py and server.py. I open another port to receive messages from views.py. Once the borrow button is pressed, a socket client in views.py will build up and send arguments and other messages to the server.

Using Memcached with Flask Blueprints

Within the application context, I can't seem to set objects in memcached. Logs indicate that I connect to memcached, but when I attempt to set an object the set function returns "0" or False. Outside of the application context, I can connect to the server IP and port, and easily get and set objects. Here is my setup:
application/__init__.py
class App(Flask):
def __init__(self):
super(App, self).__init__(__name__)
self.config.from_object('app.config')
self.config.from_object('app.deployments.Prod')
logging.basicConfig(filename=self.config['LOG_PATH'] + config.LOG_FILE, level=logging.INFO, format=config.LOG_FORMAT, datefmt='%m/%d/%Y %I:%M:%S')
self.static_folder=config.STATICS
self.before_request(self.init_dbs)
self.teardown_request(self.teardown)
self.after_request(self.teardown)
try:
self.init_session()
self.init_login()
self.init_templates()
except Exception as e:
logging.info(e)
def init_dbs(self):
g.ES = init_elasticsearch(hosts=self.config['ES_HOSTS'])
g.MEMCACHED = init_memcached(host=self.config['MEMCACHED_HOST'],port=self.config['MEMCACHED_PORT'])
...
cache/__init__.py
from werkzeug.contrib.cache import MemcachedCache
import gevent
import logging
def init_memcached(host,port):
memcached_connected = False
while not memcached_connected:
try:
MEMCACHED = MemcachedCache([host + ':' + str(port)])
memcached_connected = True
except Exception as e:
logging.info("Memcached not connected")
logging.error(e)
gevent.sleep(1)
return MEMCACHED
controllers/page.py
from flask import Blueprint, request, render_template, url_for, flash, g, redirect
from flask.views import MethodView
from flask.ext.login import current_user
from json import dumps
from app import config
...
items = Blueprint(
'items',
__name__,
template_folder=config.TEMPLATES,
)
class Item(MethodView):
def get(self,item):
result = g.MEMCACHED.get('item')
if result is None:
...
g.MEMCACHED.set('item', result, timeout=60)
return render_template('item.html',result=result)
items.add_url_rule("/path/<item>", view_func=Item.as_view('item'))
I'm assuming this has something to do with using memcached within the g object. I'd prefer setting the connection to memcached once, as I'm doing with the dbs, but it seems like memcached doesn't respond in the same way.

Rabbitmq listener using pika in django

I have a django application and I want to consume messages from a rabbit mq. I want the listener to start consuming when I start the django server.I am using pika library to connect to rabbitmq.Proving some code example will really help.
First you need to somehow run your application at the start of the django project
https://docs.djangoproject.com/en/2.0/ref/applications/#django.apps.AppConfig.ready
def ready(self):
if not settings.IS_ACCEPTANCE_TESTING and not settings.IS_UNITTESTING:
consumer = AMQPConsuming()
consumer.daemon = True
consumer.start()
Further in any convenient place
import threading
import pika
from django.conf import settings
class AMQPConsuming(threading.Thread):
def callback(self, ch, method, properties, body):
# do something
pass
#staticmethod
def _get_connection():
parameters = pika.URLParameters(settings.RABBIT_URL)
return pika.BlockingConnection(parameters)
def run(self):
connection = self._get_connection()
channel = connection.channel()
channel.queue_declare(queue='task_queue6')
print('Hello world! :)')
channel.basic_qos(prefetch_count=1)
channel.basic_consume(self.callback, queue='queue')
channel.start_consuming()
This will help
http://www.rabbitmq.com/tutorials/tutorial-six-python.html