Shared state between WebSocketHandler and RequestHandler - websocket

I am writing chat app and I need session data in my websocket handler. Problem is that login and logout is done by AJAX request, and updating/clearing cookie cannot be seen by websocket handler - it sees only state from the time website was initially rendered. But it cannot see, when cookie is changed by AJAX request. Do you have any idea, how this can be implemented?
import json
import tornado.web
import tornado.websocket
class BaseHandler(object):
#property
def db(self):
return self.application.db
def get_current_user(self):
id = self.get_secure_cookie('user')
user = self.db.get_user_by_id(id) if id else ''
return user
class JsonHandler(BaseHandler, tornado.web.RequestHandler):
def write(self, response):
if response:
response = json.dumps(response)
TemplateHandler.write(self, response)
#property
def body(self):
return json.loads(self.request.body.decode())
class WebSocketHandler(BaseHandler, tornado.websocket.WebSocketHandler):
pass
class Login(JsonHandler):
def post(self):
login = self.body.get('login')
password = self.body.get('password')
user = self.db.validate_user(login, password)
if user:
self.set_secure_cookie('user', str(user['id']))
self.write(user)
else:
raise tornado.web.HTTPError(401)
class Logout(JsonHandler):
def post(self):
self.clear_cookie('user')
self.write('')
class WebSocket(WebSocketHandler):
clients = set()
def open(self):
WebSocket.clients.add(self)
def on_close(self):
WebSocket.clients.remove(self)
def on_message(self, message):
# how to use method self.get_current_user()
# it uses self.get_secure_cookie('user')
# but it is not updated dynamically by Login and Logout handlers
# because websocket does not share state with them
for client in WebSocket.clients:
client.write_message(message)

Related

can't loop function in websockets

I trying to use websockets to receive real time update on trading orders. If it sent "closed" then to run some function.
The problem is It only run in "one time". I have tried but still in the dark.
EDIT. I try to make it in class but I don't know why I can't pass variable(self.status) to another function pending(self) in same class
class check:
async def handler(self):
async with websockets.connect('wss://ftx.com/ws/') as ws:
# authentication things
async for data in ws:
message = json.loads(data)
search = dictpy.DictSearch(data=message, target='closed')
self.status = search.result
print(self.status) # It contain 'closed'
async def pending(self):
print(self.status) # but It didn't even recieve from handler
if self.status == [['data.status', {'status': 'closed'}]]:
t3.start()
t4.start()
t3.join()
t4.join()

Running Quart and Telethon with multiple clients (sessions)

I'm trying to run Quart+Telethon with multiple clients. This example shows one global client. I have this working.
Now I need my app to handle multiple users simultaneously logging in and doing stuff.
This post suggests using asyncio.gather.
How do I need to change my code such that I can have multiple people logging in?
Here I placed the bulk of Quart functionality into the work() function (wherever client is referenced). In the def main() I start the app and then invoke asyncio.gather.
When running the app with two work() functions (two clients) in asyncio.gather I get error "AssertionError: Handler is overwriting existing for endpoint phone_form".
And if I run only with one work() function I get a different error: "ConnectionError('Cannot send requests while disconnected')"
What am I missing?
Thx
import os
import asyncio
from dotenv import load_dotenv
from quart import Quart, render_template_string, request, render_template
from telethon import TelegramClient
load_dotenv('.env')
API_ID = int(os.getenv('API_ID'))
API_HASH = str(os.getenv('API_HASH'))
app = Quart(__name__)
async def work(client):
async with client:
#app.route("/")
async def phone_form():
return await render_template('phone_form.html')
#app.route("/validation_form", methods=['POST'])
async def validation_form():
""" Ask the user for the confirmation code they just got. """
global phone
# Check form parameters (phone/code)
form = await request.form
if 'phone' in form:
phone = form['phone']
await client.send_code_request(phone)
return await render_template('validation_form.html', phone_nr=phone)
async def main():
import hypercorn.asyncio
# create task so that starting hypercorn server is no blocking functions that come after it
server = asyncio.create_task(hypercorn.asyncio.serve(app, hypercorn.Config()))
# have many clients here, using the app asynchronously
await asyncio.gather(
work(TelegramClient('user1', API_ID_, API_HASH)),
work(TelegramClient('user2', API_ID, API_HASH)),
)
# this is to start blocking - means after this subsequent functions will need to wait until hypercorn is finished (hypercorn runs forever!)
# this await also lets server run indefinitely
await server
if __name__ == '__main__':
asyncio.run(main())
The code is trying to set N function handlers to the same routes, that's what causing the error here. I believe you need a different approach here - make adding sessions a continuous process with routes dealing with the last (new) session.
I'm not sure how hypercorn server should be started so using it's part from your example.
eg.
API_ID = int(os.getenv('API_ID'))
API_HASH = str(os.getenv('API_HASH'))
app = Quart(__name__)
clients = []
async def work():
new_cli = None
phone = None
#app.route("/")
async def index():
""" Some page containing 'new session' button redirecting to /phone_form """
new_cli = None
phone = None
return render_template('index.html')
#app.route("/phone_form")
async def phone_form():
num = len(clients) + 1
new_cli = TelegramClient(f'user{num}', API_ID_, API_HASH)
await new_cli.connect()
return await render_template('phone_form.html')
#app.route("/validation_form", methods=['POST'])
async def validation_form():
""" Ask the user for the confirmation code they just got. """
# Check form parameters (phone/code)
form = await request.form
if 'phone' in form:
phone = form['phone']
await client.send_code_request(phone)
return await render_template('validation_form.html', phone_nr=phone)
#app.route("/confirm_code", methods=['POST'])
async def confirm_code():
""" Finish auth process. """
form = await request.form
if 'code' in form:
await new_cli.sign_in(phone, form['code'])
clients.append(new_cli)
new_cli = None
phone = None
async def main():
import hypercorn.asyncio
server = asyncio.create_task(hypercorn.asyncio.serve(app, hypercorn.Config()))
await work()
await server
if __name__ == '__main__':
asyncio.run(main())
You may also want to add checkers if new client session is present. Resulted sessions can be used later.

multiple user-session managemnet

im trying to build an flask application with user authentication. each user has different pages to be shown. the issue im facing is whenever user A logs in his username is saved in session variable and i use this to prevent users toggling through url without logging in first. but whenever user B logs in,user A is able to see what user B can. the previous session data is being over written. it would amazing if anyone can let me know how do i declare another session each time a new user walks in.
from flask import Flask,render_template,redirect,session,request,g,make_response,url_for
import psycopg2
import os
appt = Flask(__name__)
appt.secret_key= os.urandom(16)
conn=psycopg2.connect( database="one",user="postgres",password="0000",host="localhost",port="5432" )
cursor = conn.cursor()
cursor.execute("select username from use")
us=cursor.fetchall()
cursor.execute("select password from use")
psw= cursor.fetchall()
i use the above database data for user authentication
#appt.route('/',methods=['GET','POST'])
def index():
if request.method=='POST':
global user_name
user_name=request.form['username'] # make it global
pass_word=request.form['password']
for i in range(len(us)):
if user_name in us[i][0]:
new=psw[i][0]
if new==pass_word:
print(session.get('user'))
if 'user' not in session:
print('new user')
user = request.form['username'] # setting user to cookie with userid: username
resp = make_response(render_template('monitor.html'))
resp.set_cookie('userID', user) # setting a cookie
print(request.cookies.get('userID'))
return resp
else:
print('old user')
return session.get('user')
else:
return render_template('login.html',info="invalid user")
return render_template('login.html')
#appt.route('/logout', methods=['GET', 'POST'])
def logout():
print("hello")
if g.user:
if request.method == 'GET':
print("hello")
print(session['user'][0])
session.pop('user',None)
return render_template("login.html")
else:
return redirect("monitor.html")
#appt.before_request
def before_request():
#g.user=None
if 'user' in session:
g.user = session['user']
if __name__ == '__main__':
appt.run(debug=True)

How to pass MQTT message object (including topic and payload) from on_message callback function across django using queue?

I am developing a Django Rest API project to read sensors' data and display their realtime values in the browser.
I have an __init__.py file inside the utils folder that initiates the MQTT connection and subscribes to the sensor topics.
import paho.mqtt.client as mqtt
from device.models import Device, RecordLog
import datetime
from queue import Queue
# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
print("Connected with result code "+str(rc))
# Subscribing in on_connect() means that if we lose the connection and
# reconnect then subscriptions will be renewed.
# client.subscribe("$SYS/#")
for topic in Device.object.values_list('unique_id', flat=True):
client.subscribe(topic)
q = Queue()
message_list = []
# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
print(msg.topic+" "+str(msg.payload.decode('utf-8')))
message_list.append(msg)
q.put(message_list)
#insert into database
try:
r = RecordLog(device=Device.objects.filter(unique_id=msg.topic).values_list('name', flat=True).first(), topic=msg.topic, record=str(msg.payload.decode('utf-8')), dt_inserted=datetime.datetime.now())
r.save()
except:
print("Error on storing data into database")
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect("mqtt.eclipse.org", 1883, 60)
client.loop_start()
Using this piece of code I insert the sensor's data into the database and I want to put them into a queue and call them in views.py.
from utils import on_message, on_connect
from queue import Queue
q = Queue()
# MQTT Data Serializer
class Sensor:
permission_classes = (permissions.IsAuthenticated,)
serializer_class = DeviceSerializer
def pusher(self):
queryset = Device.objects.filter(unique_id=q.get().topic).values_list('place', 'name', 'device_type', 'unique_id')
data = {
"device_info": queryset,
"sensor_data": q.get().payload
}
return JsonResponse(data)
and my urls.py is...
from .views import Sensor
urlpatterns = [
path('device/sensors/', Sensor.pusher),
]
But finally, my endpoint URL remains to load and returns no response.
I will be grateful if you help me to solve this problem or make an alternative solution.

Pylons, FormEncode and external validation

I'm building a web frontend to a server-side application, using Pylons 1.0.
Right now I'm writing the first form, and I'm facing a problem concerning validation.. Using FormEncode and the #validate decorator I can easily validate the user input from a client-side perspective, but when I submit the data to the server, it may perform additional checks and eventually throw back exceptions that I need to show to the user.
My question: is there a concise way to integrate/emulate this exception handling into the FormEncode/validate flow? For example, redisplay the form with filled fields and an error message, as would happen if the exception had come from the #validate itself?
Here's what I have at the moment:
def edit(self, id):
return render('/edit_user.mako')
#validate(schema=form.UserForm(), form="edit")
def add_user(self):
if request.POST:
u = helpers.load_attributes(User(), self.form_result)
try:
model.save_to_server(u)
except MyBaseException, exc:
helpers.flash(unicode(exc))
return self.edit()
In this way, in case of a server-side exception I can see the "flash" message but the form of course will have empty fields :/
I like to implement:
from formencode import htmlfill
def create(self):
if request.params:
try:
Post.validate(request.paramse)
post = helpers.load_attributes(Post(), request.params)
model.save_to_server(post)
flash('OK', 'success')
redirect(...)
except InvalidException as e:
for key, message in e.unpack_errors().iteritems():
flash(message, 'error')
return htmlfill.render(render('/blogs/create.html'), request.params)
where my Post.validate:
#staticmethod
def validate(data):
schema = PostSchema()
schema.to_python(data)
In this way, if is the first time (request.params empty) html fills form with nothing, when user send datas html fills form with request.params
Another way (inspired by this answer) is to write a decorator similar to #validate that would catch the desired exceptions and use htmlfill to display their message:
def handle_exceptions(form):
def wrapper(func, self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except MyBaseException, e:
request = self._py_object.request
errors = { "exception" : unicode(e) }
params = request.POST
decoded = params.mixed()
request.environ['REQUEST_METHOD'] = 'GET'
self._py_object.tmpl_context.form_errors = errors
request.environ['pylons.routes_dict']['action'] = form
response = self._dispatch_call()
# If the form_content is an exception response, return it
if hasattr(response, '_exception'):
return response
htmlfill_kwargs2 = {}
htmlfill_kwargs2.setdefault('encoding', request.charset)
return htmlfill.render(response, defaults=params, errors=errors,
**htmlfill_kwargs2)
return decorator(wrapper)
The decorator would be used like:
#handle_exceptions("edit")
#validate(schema=form.UserForm(), form="edit")
def add_user(self):
if request.POST:
u = helpers.load_attributes(User(), self.form_result)
model.save_to_server(u)

Resources