I'm trying to broadcast a message to all the users using flask and angular, I'm trying to achieve this using socketio but since I'm new to socketio, I'm unable to implement it as expected.
Basic idea is to pick a message from db table and broadcast to all users using via API call.
The server should show the new message without the website being refreshed by the user.
Python Code:
import socketio
from aiohttp import web
app = Flask(__name__)
sio = socketio.AsyncServer(async_mode='aiohttp', logger=True, engineio_logger=True)
sio.attach(app)
#sio.on('join_room', namespace='/maintenance')
def handle_message(sid, room):
sio.enter_room(sid, room=room, namespace='/maintenance')
#sio.on('getMessage', namespace='/maintenance')
async def handle_message(sid, maintenance_room):
await sio.emit('getMaintenanceResponse', 'json_object', namespace='/maintenance', room=maintenance_room)
#sio.on('addMessage', namespace='/maintenance')
async def add_message(sid, message_data, maintenance_room):
await sio.emit('getMaintenanceResponse', 'json_object', namespace='/maintenance', room=maintenance_room)
Angular Code:
this.socket.createMaintenanceRoom('maintenance');
this.socket.getMaintenance('maintenance').subscribe(response => {
if (response) {
this.maintenanceNotesData = response;
}
},
err => console.error('Observer got an error: ' + err),
() => console.log('Observer got a complete notification'));
Related
I have a rest api server which makes call to some other Apis,I am accessing the data I get from the server on a react js frontend,But for certain usecases I need to fetch real time data from backed,is there any way do this together,below is my code
from flask import Flask,request
from flask_cors import CORS
from tuya_connector import TuyaOpenAPI, TUYA_LOGGER
app = Flask(__name__)
CORS(app)
#app.get("/api/device/<string:deviceid>")
def getdata(deviceid):
ACCESS_ID = ""
ACCESS_KEY = ""
API_ENDPOINT = ""
# Enable debug log
# Init OpenAPI and connect
openapi = TuyaOpenAPI(API_ENDPOINT, ACCESS_ID, ACCESS_KEY)
openapi.connect()
# Set up device_id
DEVICE_ID = deviceid
# Call APIs from Tuya
# Get the device information
response = openapi.get("/v1.0/devices/{}".format(DEVICE_ID))
return response
I want to have traditional request response service along with real time data fetching
Websockets endpoints are exactly what you're looking for. If that is not too late, I'd recommend switching to FastAPI which supports WebSockets "natively" (out-of-the-box) - https://fastapi.tiangolo.com/advanced/websockets
If you need to keep using Flask, there are a few packages that allow you to add WebSockets endpoints: https://flask-sock.readthedocs.io/en/latest/
With FastAPI, this is that simple:
#app.get("/")
async def get():
return {"msg": "This is a regular HTTP endpoint"}
#app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message text was: {data}")
I'm sending the stream of data from flask server via yield. I'm able to see this stream if I go directly to api url. However I don't know how to receive it on frontend. I would appreciate the help. Here is how my backend looks like:
STREAM
`
def redis_stream():
global lock
channel = r.pubsub()
channel.subscribe('CellGridMapClose')
for msg in channel.listen():
if msg['type'] == 'message':
obj = tm.CellGridMapping()
obj.ParseFromString(msg['data'])
objects = obj.objects
movement = []
for vehicle in objects:
x, y = vehicle.pos.x, vehicle.pos.y
movement.append({'posx': x/50, 'posy': y/40})
yield bytes(json.dumps(movement), 'utf-8')
`
ROUTE
`
#app.route('/redis-stream')
def redis_data():
return Response(redis_stream(), mimetype='application/json')
`
This is how my frontend looks. I've tried many variants. This is the last one, however it's not working
FRONTEND
`
const response = await axios.get("/redis-stream", {responseType: 'arraybuffer'});;
console.log(response.data);
`
I'm attempting to send consumers.py information to display on the client end outside of consumers.py.
I've referenced Send message using Django Channels from outside Consumer class this previous question, but the sub process .group_send or .group_add don't seem to exist, so I feel it's possible I'm missing something very easy.
Consumers.py
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync
class WSConsumer(WebsocketConsumer):
def connect(self):
async_to_sync(self.channel_layer.group_add)("appDel", self.channel_name)
self.accept()
self.render()
appAlarm.py
def appFunc(csvUpload):
#csvUpload=pd.read_csv(request.FILES['filename'])
csvFile = pd.DataFrame(csvUpload)
colUsernames = csvFile.usernames
print(colUsernames)
channel_layer = get_channel_layer()
for user in colUsernames:
req = r.get('https://reqres.in/api/users/2')
print(req)
t = req.json()
data = t['data']['email']
print(user + " " + data)
message = user + " " + data
async_to_sync(channel_layer.group_send)(
'appDel',
{'type': 'render', 'message': message}
)
It's throwing this error:
async_to_sync(channel_layer.group_send)(
AttributeError: 'NoneType' object has no attribute 'group_send'
and will throw the same error for group_add when stripping it back more to figure out what's going on, but per the documentation HERE I feel like this should be working.
To anyone looking at this in the future, I was not able to use redis or even memurai in Windows OS due to cost. I ended up using server side events (SSE), specifically django-eventstream, and so far it's worked great as I didn't need the client to interact with the server, for a chat application this would not work.
Eventstream creates an endpoint at /events/ the client can connect to and receive a streaming http response.
Sending data from externalFunc.py:
send_event('test', 'message', {'text': 'Hello World'})
Event listener in HTML page:
var es = new ReconnectingEventSource('/events/');
es.addEventListener('message', function (e) {
console.log(e.data);
var source = new EventSource("/events/")
var para = document.createElement("P");
const obj = JSON.parse(event.data)
para.innerText = obj.text;
document.body.appendChild(para)
}, false);
es.addEventListener('stream-reset', function (e) {
}, false);
I have a React Client which needs to consume messages emitted by a flask_socketio server, it works fine if I emit a single message on 'connect' however if i keep emitting it in a while loop it doesn't get picked up on the client-side. I presume it could be due to action being performed in an active thread. I cannot find any posts online that talks about the issue.
Below is my React js client code.
class MyClient extends Component {
constructor() {
super();
this.state = {
endpoint:"http://127.0.0.1:5000",
response: ""
};
}
componentDidMount() {
console.log("test")
const {endpoint} = this.state;
//Very simply connect to the socket
const socket = socketIOClient(endpoint);
//Listen for data
socket.on("connect", dd => {
socket.on("log", dx => this.setState({response: dx.data}));
});
}
render() {
const {response} = this.state;
return (
<div>
<h1>Test {response}</h1>
{console.log(response)}>
</div>
)
}
}
The client looks for "log" to be emitted from the server, for now I added time to mock changing data.
When client loads i see the data, and when I disconnect and reconnect the server I get to see the new data on the client. Am wondering how do I emit the data continuously
from flask import Flask, render_template, request, jsonify
from flask_socketio import SocketIO, emit
from flask_cors import CORS
from datetime import datetime
import time
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
app = Flask(__name__)
app.config['SECRET_KEY'] = 'justasecretkeythatishouldputhere'
socketio = SocketIO(app,cors_allowed_origins="*")
CORS(app)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/api')
def api():
query = dict(request.args)
socketio.emit('log', dict(data=str(query)), broadcast=True)
return jsonify(dict(success=True, message='Received'))
#socketio.on('connect')
def on_connect():
time.sleep(1)
t = str(time.clock())
print(t)
socketio.emit('log', {'data': t, 'time': t})
if __name__ == '__main__':
socketio.run(app,port=5000)
Below loop does not work, should i run it as a background thread or is there an async property that i need to add to socketio. Any help is greatly appreciated.
#socketio.on('connect')
def on_connect():
while True:
time.sleep(1)
t = str(time.clock())
print(t)
socketio.emit('log', {'data': t, 'time': t})
In your client side you can make use of setInterval method to emit an event, after every few milliseconds.
On server side you can emit the event via which you can send the data.
So, all together, it will be something like below:
Client Side:
setInterval(function() {
socket.emit('time');
}, 1) // 1 millisecond is almost close to continues
socket.on('time', function(data) {
console.log(data)
});
Server Side:
#socket.on('time')
def time():
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
emit('time', current_time)
In the console log of your browser, you can see the continues data getting logged.
I hope this helps.
Note: Client side code, which I have provide here, is of pure JavaScript. Since, React is based on JavaScript, there will be some or the other way to use setInterval.
i've just started working with line-bot and followed the tutorial here: https://developers.line.biz/en/docs/messaging-api/building-bot/
However, I still don't understand how I can connect with my line app account, to send messages, and have these messages appear back in python.
The below is the script I copied from line tutorial.
from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage
app = Flask(__name__)
line_bot_api = LineBotApi('foo', timeout=20)
handler = WebhookHandler('bar')
user_profile = 'far'
#app.route("/", methods=['GET'])
def home():
profile = line_bot_api.get_profile(user_profile)
print(profile.display_name)
print(profile.user_id)
print(profile.picture_url)
print(profile.status_message)
return '<div><h1>ok</h1></div>'
#app.route("/callback", methods=['POST'])
def callback():
# get X-Line-Signature header value
signature = request.headers['X-Line-Signature']
# get request body as text
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)
# handle webhook body
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400)
return 'OK'
#handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text='hello world'))
if __name__ == "__main__":
app.run(debug=True)
What am I missing, or how can I connect with the line app to send and receive messages?
I followed that tutorial and was able to successfully create a bot that just echoes messages in uppercase:
Your question is how to "connect" your bot's code with the LINE app. The three most important parts of the tutorial are probably:
Adding the bot as a friend, you do this by scanning its QR code with the LINE app
When you create a channel for your bot, you need to enable "Webhooks" and provide an https endpoint which is where LINE will send the interaction events your bot receives. To keep this simple for the purposes of this answer, I created an AWS Lambda function and exposed it via API Gateway as an endpoint that looked something like:
https://asdfasdf.execute-api.us-east-1.amazonaws.com/default/MyLineBotFunction. This is what I entered as the Webhook URL for my bot.
Once you are successfully receiving message events, responding simply requires posting to the LINE API with the unique replyToken that came with the message.
Here is the Lambda function code for my simple yell-back-in-caps bot:
import json
from botocore.vendored import requests
def lambda_handler(event, context):
if 'body' in event:
message_event = json.loads(event['body'])['events'][0]
reply_token = message_event['replyToken']
message_text = message_event['message']['text']
requests.post('https://api.line.me/v2/bot/message/reply',
data=json.dumps({
'replyToken': reply_token,
'messages': [{'type': 'text', 'text': message_text.upper()}]
}),
headers={
# TODO: Put your channel access token in the Authorization header
'Authorization': 'Bearer YOUR_CHANNEL_ACCESS_TOKEN_HERE',
'Content-Type': 'application/json'
}
)
return {
'statusCode': 200
}