Sending many requests in one websocket connection without reconnect - python-3.x

My python program work so slow, because make socket reconnect for any request. I want make one connect and send request, with out reconnect
My functions in file send_by_socket.py, some other function and class call send_to_socket for send logmessage. Now it work, but very slow. Reason - make new connect for any message. I want single connection or poll for use it without reconnect. How make it, possibel have good example with sourcecode?
import asyncio
import websockets
from logging import StreamHandler
import json
async def async_send(message):
async with websockets.connect('wss://****.com/chat') as web_socket:
await web_socket.send(message)
class WebSocketHandler(StreamHandler):
def __init__(self):
StreamHandler.__init__(self)
def emit(self, record):
msg = json.dumps({'log': {'message': record.message, 'date': record.asctime, 'level': record.levelname}})
try:
asyncio.get_event_loop().run_until_complete(async_send(msg))
except ConnectionRefusedError:
pass
def send_to_socket(msg_dict):
msg = json.dumps(msg_dict)
try:
asyncio.get_event_loop().run_until_complete(async_send(msg))
except ConnectionRefusedError:
pass
Now program spend about 1 - 1.2 sec for request. I try
con = websockets.connect('wss://****.com/chat')
con.send('some thing')
but have error AttributeError: 'Connect' object has no attribute 'send'

python
import asyncio
import websockets
from logging import StreamHandler
import json
import time
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
#singleton
class SendToWebSocket:
"""
Send message in web-socket, use one connection for sending.
Try make new connection, if old is lost.
"""
__ws = None
__url = "wss://***.com/chat"
def __init__(self):
self.retryTime = 0
self.retryRepeat = 30
self.__create_connect()
#asyncio.coroutine
def __create_connect(self):
if (time.time() - self.retryTime) > self.retryRepeat:
try:
self.__ws = yield from websockets.connect(self.__url)
self.retryTime = 0
except ConnectionRefusedError:
self.retryTime = time.time()
def send(self, message):
t = type(message)
if t is dict:
msg = json.dumps(message)
elif t is str:
msg = message
else:
raise ValueError("Message must be str or dict. Received %s" % type(t))
if self.__ws is not None:
try:
asyncio.get_event_loop().run_until_complete(self.__async_send(msg))
# print('Send normal')
except ConnectionRefusedError:
# print("Can't send")
# try recreate connect
self.__create_connect()
else:
asyncio.get_event_loop().run_until_complete(self.__create_connect())
async def __async_send(self, message):
await self.__ws.send(message)
class WebSocketHandler(StreamHandler):
"""Custom handler for logging library"""
def __init__(self):
StreamHandler.__init__(self)
self.web_socket = SendToWebSocket()
def emit(self, record):
msg = json.dumps({'log': {'message': record.message, 'date': record.asctime, 'level': record.levelname}})
try:
self.web_socket.send(msg)
except ConnectionRefusedError:
pass

Related

Chrome makes block request

I made such a minimal example that completely repeats the behavior of my code. I make requests from firefox and chrome. I noticed that after making a request from chrome, firefox ceases to receive answers. After some research, I realized that the server response with error on localhost:8000/favicon.ico path request. After receiving error 404 once, chrome after each response from server creates another connection to the server, but does not send data, which causes a lock on the recv function.
File "/usr/lib/python3.7/socket.py", line 589, in readinto
return self._sock.recv_into(b)
I found that I can set the connection timeout for the handler class, it is taken into account in the StreamRequestHandler:r:
if self.timeout is not None:
self.connection.settimeout(self.timeout)
But I am embarrassed that there is no information about this in the documentation
https://docs.python.org/3/library/socketserver.html#socketserver.BaseRequestHandler.handle
import logging
import json
import http.server
from http import HTTPStatus
from typing import Optional
from urllib.parse import urlparse, parse_qs
import socketserver
from threading import Thread
import traceback
from functools import wraps
import sys, os
project_dir = os.path.abspath(os.curdir)
sys.path.append(project_dir)
logging.getLogger().setLevel("DEBUG")
class RESTHandler(http.server.BaseHTTPRequestHandler):
"""
Rest router for api methods
"""
def __init__(self, *args, **kwargs):
logging.info(f"Creating RESTHandler obj. Args: {args}, kwargs: {kwargs}")
super().__init__(*args, **kwargs)
def end_headers(self) -> None:
self.send_header('Access-Control-Allow-Origin', '*')
http.server.BaseHTTPRequestHandler.end_headers(self)
# noinspection PyPep8Naming
def do_GET(self):
logging.info(self.path)
url = urlparse(self.path)
if "favicon.ico" in url.path:
self.send_error(HTTPStatus.NOT_FOUND, message='Unknown api path.')
return
self.send_response(HTTPStatus.OK)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"resp":"I am OK", "int": 5}, ensure_ascii=False).encode('utf-8'))
class ApiService():
DEFAULT_API_PORT = 8000
DEFAULT_API_HOST = ''
def __init__(self, ui_service = None, host: Optional[str] = None, port: Optional[int] = None):
self.ui_service = ui_service
self.host = host or self.DEFAULT_API_HOST
self.port = port or self.DEFAULT_API_PORT
def _run(self):
while True:
try:
with socketserver.TCPServer((self.host, self.port), RESTHandler, bind_and_activate=False) as httpd:
logging.info("Starting server....")
httpd.allow_reuse_address = True
httpd.server_bind()
httpd.server_activate()
logging.info(f"Serving API at {self.host}:{self.port}")
httpd.serve_forever()
break
except Exception as e:
tb_list = traceback.format_exception( type(e), e, tb=e.__traceback__)
tb_list = [ s.replace("\n", "") for s in tb_list ]
tb_str = "; ".join(tb_list)
logging.error(f"Unexpected exception while http-server was working: {tb_str}")
def run(self, in_thread=True):
if in_thread:
t = Thread(target=self._run)
t.start()
else:
self._run()
if __name__ == '__main__':
ApiService().run(in_thread=False)
I guess, Chrome uses web browsers pre-opening sockets, on which TCPServer would wait indefinitely in my case. But I am still interesting why only after 404 and what about legitimacy of using timeout of request handler.

Error from callback function in python websocket: line 346, in _callback callback(self, *args)

I used the Gemini websocket code for a BTC feed but a callback error keeps popping up. However, it occasionally does run the code correctly. I have used 'price' and 'quantity' to test the code before I use the actual variables I want.
Error from running my gemini websocket code
Here is the code:
import websocket, ssl, json
import _thread as thread
websocket._logging._logger.level = -99
bestbid = {}
bestask = {}
top_of_book = 0
def on_message(self, message):
global bestbid, bestask
bestbid = json.loads(message)
bestask = json.loads(message)
print("Message received!")
print("{} {}".format(bestbid["price"], bestask ["quantity"]))
def on_error(self, error):
print(error)
def on_close(self):
print("Connection closed!")
def on_open(self):
print("Connection opened!")
def run(*args):
ws.send(logon_msg)
thread.start_new_thread(run, ())
if __name__ == "__main__":
logon_msg = '{"type": "subscribe","subscriptions":[{"name":"l2","symbols":["BTCUSD"]}]}'
websocket.enableTrace(True)
ws = websocket.WebSocketApp("wss://api.gemini.com/v2/marketdata/",
on_message = on_message,
on_error = on_error,
on_close = on_close,
on_open = on_open)
ws.on_open = on_open
ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
You should check that bestbid (bestbuy) JSON object has fields "price" and quantity". Errors most probably are caused by heartbeat or some diagnostic messages.

ERROR:root:Error in xyz There is no current event loop in thread 'Thread-7'

I am trying to achieve fire_and_forget functionality and here are the details.
Async decorater i am trying to use:
import asyncio
import time
def fire_and_forget(f):
def wrapped(*args, **kwargs):
return asyncio.get_event_loop().run_in_executor(None, f, *args, *kwargs)
return wrapped
My Async call using above decorator:
#fire_and_forget
def call_async(self, req, body, headers_params):
logger.info("Calling ASYNC")
try:
f = urllib.request.urlopen(req)
response = f.read()
f.close()
except Exception as e:
logger.exception("api exception %s" % e)
return None
# Parse and return the response
try:
res = self._parse_response(response)
except Exception as e:
logger.exception("Exception in parsing response of %s" % e)
res = None
logger.debug("clevertap response: {}".format(res))
My Flask app calling test_xyz which in turns fire the above fire and forget call_async:
from flask import Flask, jsonify, request
from call_xyz import test_xyz
app = Flask(__name__)
#app.route('/events_dm')
def events_dm():
session_id = request.args.get('sessionId', "11111111")
test_obj = test_xyz(session_id)
test_obj.track_test()
return jsonify({"success": True})
app.run(
host='0.0.0.0',
port=8091,
debug=True,
threaded=True
)
I am not able to understand where to set my event loop correctly so that i don't get the error: "Error in xyz There is no current event loop in thread 'Thread-7'" and my events get fired correctly.
Flask is not asyncio-compatible, so you shouldn't attempt to use asyncio within it.
Besides, you're not actually using asyncio's functionality, but run_in_executor which calls into concurrent.futures, i.e. uses threads under the hood. If that's what you need, you can create an executor directly and just submit your function to it. For example:
import concurrent.futures
_pool = concurrent.futures.ThreadPoolExecutor()
def fire_and_forget(f):
def wrapped(*args, **kwargs):
return _pool.submit(lambda: f(*args, **kwargs))
return wrapped
You must probably go through once asyncio usage and its main heart event loop understanding.
Similar issue which might help you to understand.
Here is the tutorial with some good explanation
Just give some flow for how use coroutine with normal Flask app here is the sample one.
import asyncio
import datetime
from flask import Flask, jsonify, request
app = Flask(__name__)
def print_now():
print(datetime.datetime.now())
async def keep_printing(name: str="") -> None:
print(name, end=" ")
print_now()
await asyncio.sleep(1.50)
async def main(num_times):
result = await asyncio.gather(
keep_printing("first"),
keep_printing("second"),
keep_printing("third"),
)
return result
def execute_statement():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(main(2))
loop.close()
#app.route('/events_dm')
def events_dm():
execute_statement()
return jsonify({"success": True})
app.run(
host='0.0.0.0',
port=8091,
debug=True,
threaded=True
)
Output when you hit /events_dm
first 2020-07-18 00:46:26.452921
second 2020-07-18 00:46:26.452921
third 2020-07-18 00:46:26.452921
127.0.0.1 - - [18/Jul/2020 00:46:27] "GET /events_dm HTTP/1.1" 200 -

How to reconnect to a websocket when connection fails in python

Hi I created async websocket client which I can receive and send messages asynchronously.
Here is the clint class that I use.
import websockets
import asyncio
from models import Heartbeat
from model_helper import ModelHelper
from json_helper import JSONHelper
from os.path import dirname, abspath
import logging
import time
waiting_time = 0.5
class WebSocketClient():
def __init__(self, websocket_queue, configs):
self.Heartbeat = Heartbeat().message
self.websocket_queue = websocket_queue
self.configs = configs
def get_connection_string(self):
server_url = self.configs.URL + ":" + str(self.configs.Port) + self.configs.Root
return server_url
async def connect(self):
try:
server_url = self.get_connection_string()
self.connection = await websockets.client.connect(server_url)
if self.connection.open:
print("Connection stablished. Client correcly connected")
# Send greeting
await self.connection.send(self.Heartbeat)
return self.connection
else:
print("Can not connect")
except ConnectionRefusedError as err:
print("Connection Error: {}".format(err))
async def send_message_to_socket(self, connection):
while True:
message = self.websocket_queue.get_send_queue()
try:
if message is not None:
message_ = ModelHelper.to_outgoing_request_model(message)
await connection.send(message_)
else:
await asyncio.sleep(waiting_time)
except websockets.exceptions.ConnectionClosed:
print('Connection with server closed')
self.connect()
async def receive_message_from_socket(self, connection):
while True:
try:
message = await connection.recv()
obj = JSONHelper.toObject(message)
print("Received object from websocket: {}".format(obj))
#If a websocket entry has SendMessage in its Action property
#Consider it as an sms content to be sent.
if(obj.Action == "SendMessage"):
self.websocket_queue.add_received_queue(obj)
except websockets.exceptions.ConnectionClosed:
print('Connection with server closed1')
async def send_heartbeat_to_socket(self, connection):
while True:
#print("Heartbeat loop\n")
try:
await connection.send(self.Heartbeat)
await asyncio.sleep(3)
except websockets.exceptions.ConnectionClosed:
print('Connection with server closed2')
And this is the code where I listen for websocket messages:
def listen_websocket_routine(sms_queue, websocket_queue):
while True:
time.sleep(waiting_time)
#Check Websocket queue for new messages
message = websocket_queue.get_received_queue()
# If Incoming websocket JSON has SendMessage string in its Action attribute transform it to request_model
if message is not None and send_sms.Action == "SendMessage":
# Transform it to outgoing_sms_model
reset = ModelHelper.to_outgoing_sms_model(message.Data)
# Add To send_queue of sms
sms_queue.add_send_queue(reset)
finally How I initiate them with asyncio and threading.
client = WebSocketClient(websocket_queue, configs)
loop = asyncio.get_event_loop()
connection = loop.run_until_complete(client.connect())
task1 = asyncio.ensure_future(client.receive_message_from_socket(connection))
task2 = asyncio.ensure_future(client.send_heartbeat_to_socket(connection))
task3 = asyncio.ensure_future(client.send_message_to_socket(connection))
listen_websocket_thread = threading.Thread(target=listen_websocket_routine, args=(sms_queue, websocket_queue))
listen_websocket_thread.start()
loop.run_forever()
So my question is, whenever connetion breaks, I need to re establish the connection. But I am not sure where should I do that. Should it be before everytime I try to send message or receive or should I do that in more general approach?
Since update 10.0 you could do it with an async iterator, like this(example from the official doc):
async for websocket in websockets.connect(...):
try:
...
except websockets.ConnectionClosed:
continue

Send data from an other class through ws after client connected

I want to send data through websockets as soon as a client is connected.
The Data is at an other place then the Websocket Handler. How can i get the data to the client ?
The server should hold the loop and the Handler. In the connector i connect to a tcp socket to get the data out of some hardware. I expect to have not more then 6 Websockets open once a time. The Data comes as a stream out of the TCP socket.
server.py
import os
from tornado import web, websocket
import asyncio
import connector
class StaticFileHandler(web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "*")
def get(self):
self.render('index.html')
class WSHandler(websocket.WebSocketHandler):
def open(self):
print('new connection')
self.write_message("connected")
def on_message(self, message):
print('message received %s' % message)
self.write_message("pong")
def on_close(self):
print('connection closed')
public_root = 'web_src'
handlers = [
(r'/', StaticFileHandler),
(r'/ws', WSHandler),
]
settings = dict(
template_path = os.path.join(os.path.dirname(__file__), public_root),
static_path = os.path.join(os.path.dirname(__file__), public_root),
debug = True
)
app = web.Application(handlers, **settings)
sensorIP = "xxx.xxx.xxx.xxx"
if __name__ == "__main__":
app.listen(8888)
asyncio.ensure_future(connector.main_task(sensorIP))
asyncio.get_event_loop().run_forever()
connector.py
import yaml
import asyncio
class RAMReceiver:
def __init__(self, reader):
self.reader = reader
self.remote_data = None
self.initParams = None
async def work(self):
i = 0
while True:
data = await self.reader.readuntil(b"\0")
self.remote_data = yaml.load(data[:-1].decode("utf-8",
"backslashreplace"))
# here i want to emit some data
# send self.remote_data to websockets
if i == 0:
i += 1
self.initParams = self.remote_data
# here i want to emit some data after open event is
# triggered
# send self.initParams as soon as a client has connected
async def main_task(host):
tasks = []
(ram_reader,) = await asyncio.gather(asyncio.open_connection(host,
51000))
receiver = RAMReceiver(ram_reader[0])
tasks.append(receiver.work())
while True:
await asyncio.gather(*tasks)
You can use Tornado's add_callback function to call a method on your websocket handler to send the messages.
Here's an example:
1. Create an additional method on your websocket handler which will receive a message from connector.py and will send to connected clients:
# server.py
class WSHandler(websocket.WebSocketHandler):
# make it a classmethod so that
# it can be accessed directly
# from class without `self`
#classmethod
async def send_data(cls, data):
# write your code for sending data to client
2. Pass the currently running IOLoop and WSHandler.send_data to your connector.py:
# server.py
from tornado import ioloop
...
if __name__ == "__main__":
...
io_loop = ioloop.IOLoop.current() # current IOLoop
callback = WSHandler.send_data
# pass io_loop and callback to main_task
asyncio.ensure_future(connector.main_task(sensorIP, io_loop, callback))
...
3. Then modify main_task function in connector.py to receive io_loop and callback. Then pass io_loop and callback to RAMReceiver.
4. Finally, use io_loop.add_callback to call WSHandler.send_data:
class RAMReceiver:
def __init__(self, reader, io_loop, callback):
...
self.io_loop = io_loop
self.callback = callback
async def work(self):
...
data = "Some data"
self.io_loop.add_callback(self.callback, data)
...

Resources