Making pycurl request in asyncio websocket server setup - node.js

Currently, I have a simple websocket server that can handle recv and send operations. The code is as such.
async def recv_handler(websocket):
while True:
try:
message = await websocket.recv()
print(message)
except Exception as e:
print(e)
await asyncio.sleep(0.01)
async def send_handler(websocket):
while True:
try:
data = {
"type": "send",
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
await websocket.send(json.dumps(data))
except Exception as e:
print(e)
await asyncio.sleep(0.01)
async def main(websocket):
while True:
recv_task = asyncio.create_task(recv_handler(websocket))
send_task = asyncio.create_task(send_handler(websocket))
await asyncio.gather(recv_task, send_task)
async def start_server():
server = await websockets.serve(main, "", 3001)
await server.wait_closed()
if __name__ == "__main__":
asyncio.run(start_server())
This successfully runs a server, hand can handle message sent from a client node.js application using websockets as well as send updates to the client node.js application periodically.
// receive a message from the server
socket.addEventListener("message", ({ data }) => {
const packet = JSON.parse(data);
switch (packet.type) {
case "send":
console.log(packet.time)
break;
default:
break;
}
});
// send message to server
const onClickSend = () => {
if (socket.readyState !== WebSocket.OPEN) {
console.log("socket not open");
return;
} else {
socket.send(JSON.stringify({
type: "hello from client",
}));
}
}
Now, I want to include a blocking function call that sends a pycurl (or any http) request, then use the result of that pycurl request, package it into the json object, and send that to the client.
I have a sample pycurl requst that gets the weather from wttr.in
def getWeather():
# Creating a buffer as the cURL is not allocating a buffer for the network response
buffer = BytesIO()
c = pycurl.Curl()
#initializing the request URL
c.setopt(c.URL, 'wttr.in/Beijing?format="%l:+\%c+%t+%T\\n"')
#setting options for cURL transfer
c.setopt(c.WRITEDATA, buffer)
#setting the file name holding the certificates
c.setopt(c.CAINFO, certifi.where())
# perform file transfer
c.perform()
#Ending the session and freeing the resources
c.close()
#retrieve the content BytesIO
body = buffer.getvalue()
#decoding the buffer
return body.decode('utf-8')
So if we change data to include or weather,
date = {
"type" : "send",
"weather" : getWeather(),
}
and we can slightly change the node.js application case statement to print
case "send":
console.log(packet.weather)
The problem with this, I believe, is that we are making a blocking request, but I don't know enough on how to fix the problem. Currently, I can make requests, but every time the "onClickSend" is called (by pressing a button in frontend", now, we get an error saying that the "socket not open", meaning the backend is no longer handling receive messages.
So how do I handle pycurl requests in asyncio-websocket program?

Related

Is it possible for the same client to request 2 or more topics to a websocket server without needing to disconnect?

I need only 1 client to connect to my websocket server, and when it sends a message with "pattern 1" my server will send all messages to it for that pattern. If he now needs a "pattern 2" he sends the message to the server, and the server needs to keep sending the "pattern 1" and start sending the "pattern 2" messages. Without disconnecting from my server. It's possible?
When I use the while loop, the server always gets stuck on "pattern 1" and when I take the while True, it always needs messages to enter the "pattern 1" function again.
What I need is, if you received the "pattern 1" message, stay in the function and always receive the updates, if you receive the "pattern 2" message from the client, enter the "pattern 2" function and continue to receive "pattern 1" and "pattern 2" at the same time.
app.py
import websockets
import asyncio
import warnings
import json
import datetime
import concurrent.futures
warnings.filterwarnings("ignore", category=DeprecationWarning)
PORT = 7890
print("Server listening on Port: {}".format(PORT))
async def pattern_1(websocket):
while True:
await websocket.send(str("Pattern 1"))
async def pattern_2(websocket):
while True:
await websocket.send(str("Pattern 2"))
async def handler(websocket):
try:
async for message in websocket:
message = message.replace("\'", "\"")
event = json.loads(message)
print(event)
if (event['user_id'] == 1) and (event['pattern'] == "1"):
await asyncio.create_task(pattern_1(websocket))
if (event['user_id'] == 1) and (event['pattern'] == "2"):
await asyncio.create_task(pattern_2(websocket))
except Exception as e:
print("Error: {}".format(e))
# finally:
async def main():
# Start the server
async with websockets.serve(handler, "localhost", PORT):
await asyncio.Future()
asyncio.run(main())
client.py
import websockets
import asyncio
import warnings
import json
warnings.filterwarnings("ignore", category=DeprecationWarning)
async def Candle_Pattern():
url = "ws://127.0.0.1:7890"
#connect to the server
async with websockets.connect(url) as ws:
await ws.send(json.dumps({"user_id":1, "pattern":"1"}))
#I need to connect to pattern 2 also
await ws.send(json.dumps({"user_id":1, "pattern":"2"}))
while True:
msg = await ws.recv()
print(msg)
await asyncio.sleep(2)
async def main():
await asyncio.gather(
Candle_Pattern())
asyncio.run(main())

using httpx to send 100K get requests

I'm using the httpx library and asyncio to try and send about 100K of get requests.
I ran the code and received httpx.ConnectError so I opened wireshark and saw that I was getting a lot of messages saying TCP Retransmission TCP Port numbers reused
when I saw the data in wireshark and the error httpx.ConnectError I added limits = httpx.Limits(max_connections=10000) to limit the amount of active connections to 10,000 but I still get that error.
my code:
import asyncio
import json
import httpx
SOME_URL = "some url"
ANOTHER_URL = "another url"
MAX = 10000
async def search():
guids = [guid for guid in range(688001, 800000)] # 688001 - 838611
timeout = httpx.Timeout(None)
limits = httpx.Limits(max_connections=MAX)
async with httpx.AsyncClient(timeout=timeout, limits=limits) as client:
tasks = [client.get(f"{SOME_URL}{guid}", timeout=timeout) for guid in guids]
blob_list = await asyncio.gather(*tasks) # <---- error from here !!!!!
blob_list = [(res, guid) for res, guid in zip(blob_list, guids)]
guids = [guid for res, guid in blob_list]
blob_list = [json.loads(res.text)["blob_name"] for res, guid in blob_list]
async with httpx.AsyncClient(timeout=timeout, limits=limits) as client:
tasks = [client.get(f"{ANOTHER_URL}{blob}", timeout=timeout) for blob in blob_list]
game_results = await asyncio.gather(*tasks) # <---- error from here !!!!!
game_results = [(res, guid) for res, guid in zip(game_results, guids)]
game_results = [guid for res, guid in game_results]
print(game_results)
def main():
asyncio.run(search())
if __name__ == '__main__':
main()
this is a minimal version of my code there some steps in between the requests that I deleted, but I didn't touch the code that made the trouble, there are comments on the lines that I receive the errors (# <---- error from here !!!!!).
does anyone know how to solve this? or another way to send about 100K of get requests fast?
I managed to solve my problem with the following code:
(this is not the entire code, only the parts needed to send the requests, I have some stuff in between)
import asyncio
from aiohttp import ClientSession
SOME_URL = "some url"
ANOTHER_URL = "another url"
MAX_SIM_CONNS = 50
worker_responses = []
async def fetch(url, session):
async with session.get(url) as response:
return await response.read()
async def fetch_worker(url_queue: asyncio.Queue):
global worker_responses
async with ClientSession() as session:
while True:
url = await url_queue.get()
try:
if url is None:
return
response = await fetch(url, session)
worker_responses.append(response)
finally:
url_queue.task_done()
# calling task_done() is necessary for the url_queue.join() to work correctly
async def fetch_all(base_url: str, range_: range):
url_queue = asyncio.Queue(maxsize=10000)
worker_tasks = []
for i in range(MAX_SIM_CONNS):
wt = asyncio.create_task(fetch_worker(url_queue))
worker_tasks.append(wt)
for i in range_:
await url_queue.put(f"{base_url}{i}")
for i in range(MAX_SIM_CONNS):
# tell the workers that the work is done
await url_queue.put(None)
await url_queue.join()
await asyncio.gather(*worker_tasks)
if __name__ == '__main__':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
asyncio.run(fetch_all(SOME_URL, range(680_842, 840_423)))
print(worker_responses)
I used aiohttp instead of httpx and used asyncio.Queue to reduce RAM usage and it worked for me.

aiohttp says undisclosed client session even after awaiting on close

Consider following code fragment.
async def f():
http_client_session = aiohttp.ClientSession()
headers = {"developerkey": "somekey"}
body = {
"password": "somepassword",
"username": "someemail#gmail.com",
}
url = "https://localhost/login"
response_body = None
async with http_client_session.post(url, json=body, headers=headers) as response:
assert response.status == 200
response_body = await response.json()
await http_client_session.close()
return response_body()
The function f is awaited in another function. aiohttp gives the warning 'Unclosed client session' but I do not understand this as I have already awaited for it to close the session.
I've seen a similar issue previously in another project. Turns out it may be just that you need to allow some time for the session to fully close. For my project I added time.sleep(5) after the close statement to let the connection end.
See this ticket on aiohttp: https://github.com/aio-libs/aiohttp/issues/1925

How to use REQ and REP in pyzmq with asyncio?

I'm trying to implement asynchronous client and server using pyzmq and asyncio in python3.5. I've used the asyncio libraries provided by zmq. Below is my code for client(requester.py) and server(responder.py). My requirement is to use only REQ and REP zmq sockets to achieve async client-server.
requester.py:
import asyncio
import zmq
import zmq.asyncio
async def receive():
message = await socket.recv()
print("Received reply ", "[", message, "]")
return message
async def send(i):
print("Sending request ", i,"...")
request = "Hello:" + str(i)
await socket.send(request.encode('utf-8'))
print("sent:",i)
async def main_loop_num(i):
await send(i)
# Get the reply.
message = await receive()
print("Message :", message)
async def main():
await asyncio.gather(*(main_loop_num(i) for i in range(1,10)))
port = 5556
context = zmq.asyncio.Context.instance()
socket = context.socket(zmq.REQ)
socket.connect("tcp://localhost:%d" % port)
asyncio.get_event_loop().run_until_complete(asyncio.wait([main()]))
responder.py:
import asyncio
import zmq
import zmq.asyncio
async def receive():
message = await socket.recv()
print("Received message:", message)
await asyncio.sleep(10)
print("Sleep complete")
return message
async def main_loop():
while True:
message = await receive()
print("back to main loop")
await socket.send(("World from %d" % port).encode('utf-8'))
print("sent back")
port = 5556
context = zmq.asyncio.Context.instance()
socket = context.socket(zmq.REP)
socket.bind("tcp://*:%d" % port)
asyncio.get_event_loop().run_until_complete(asyncio.wait([main_loop()]))
The output that I'm getting is:
requester.py:
Sending request 5 ...
sent: 5
Sending request 6 ...
Sending request 1 ...
Sending request 7 ...
Sending request 2 ...
Sending request 8 ...
Sending request 3 ...
Sending request 9 ...
Sending request 4 ...
responder.py:
Received message: b'Hello:5'
Sleep complete
back to main loop
sent back
From the output, I assume that the requester has sent multiple requests, but only the first one has reached the responder. Also, the response sent by responder for the first request has not even reached back to the requester. Why does this happen? I have used async methods everywhere possible, still the send() and recv() methods are not behaving asynchronously. Is it possible to make async req-rep without using any other sockets like router, dealer, etc?
ZMQs REQ-REP sockets expect a strict order of one request - one reply - one request - one reply - ...
your requester.py starts all 10 requests in parallel:
await asyncio.gather(*(main_loop_num(i) for i in range(1,10)))
when sending the second request ZMQ complains about this:
zmq.error.ZMQError: Operation cannot be accomplished in current state
Try to change your main function to send one request at a time:
async def main():
for i in range(1, 10):
await main_loop_num(i)
If you need to send several requests in parallel then you can't use a REQ-REP socket pair but for example a DEALER-REP socket pair.

What to do to make 50 concurrent calls to Flask app deployed on AWS?

I am using the following python script to test an application that is running on an AWS instance,
import sys
import requests
import logging
import random
from datetime import datetime
import threading
import os
import time
logger = logging.getLogger('Intrudx')
handle = logging.FileHandler('Intrudx.log')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
handle.setFormatter(formatter)
logger.addHandler(handle)
logger.setLevel(logging.INFO)
loop_count = int(sys.argv[1])
sleep_time = int(sys.argv[2])
# CHECKING THE HEARTBEAT
def heartbeat(SessionID, SiteID):
logger.info("Starting heartbeat thread")
try:
heart_url = 'http://ec2-instance-address.com/license/heartbeat'
heart_result = requests.post(heart_url, json={
"SessionID":str(SessionID),
"SiteID" : str(SiteID)
})
if heart_result.status_code is 500:
logger.error("Heartbeat Failed with 500")
return "We Got 500"
response_text = heart_result.json()["ResponseText"]
logger.info("Heartbeat: "+str(response_text))
except Exception as e:
logger.error("Heartbeat Failed"+str(e))
# FINDING THE SERVER IP
def ip(SessionID):
logger.info("Starting get server info thread")
try:
get_server_url = 'http://ec2-instance-address.com/server/getStreamingServer'
get_server_result = requests.post(get_server_url, json={"SessionID": str(SessionID)})
result_code = get_server_result.status_code
if result_code is 500:
logger.error("GetStreamingServerInfo: " + "Failed")
return "We Got 500"
response_text = get_server_result.json()["ResponseText"]
logger.info("GetStreamingServerInfo: " + str(response_text))
except Exception as e:
logger.error("GetStreamingServerInfo: " + str(e))
def main():
for i in range(loop_count):
# LOGIN
try:
login_url = 'http://ec2-instance-address.com/user/login'
login_result = requests.post(login_url, json={
"AccountName": "Account1",
"UserID": "user2",
"UserPassword": "test"
})
result_code = login_result.status_code
if result_code is 500:
logger.error("Login: "+"Failed")
return "We Got 500"
SessionID = login_result.json()["SessionID"]
response_text = login_result.json()["ResponseText"]
logger.info("Login: "+str(response_text)+": "+ str(SessionID))
print(str(SessionID)+str(response_text))
except Exception as e:
result_code = str(e)
logger.error("Login: "+str(e))
# GET NEW SITE
try:
get_new_site_url = 'http://ec2-instance-address.com/license/getNewSite'
get_new_site_result = requests.post(get_new_site_url, json={"SessionID": str(SessionID)})
result_code = get_new_site_result.status_code
if result_code is 500:
logger.error("Login: " + "Failed")
return "We Got 500"
response_text = get_new_site_result.json()["ResponseText"]
site_id = get_new_site_result.json()["NewSiteID"]
logger.info("getNewSite: "+str(response_text)+": "+str(site_id))
except Exception as e:
result_code = str(e)
logger.error("getNewSite"+str(e))
# STARTING HEARTBEAT THREAD
try:
threading.Thread(target=heartbeat(SessionID, site_id), args=(SessionID, site_id,)).start()
except Exception as e:
logger.error("Problem starting thread: "+str(e))
# STARTING GET SERVER INFO THREAD
try:
threading.Thread(target=ip(SessionID), args=(SessionID)).start()
except Exception as e:
logger.error("Problem while starting Get Server Info Thread"+str(e))
This script is using just one user, creating one session/connection with the server to make API calls.
In a similar way, I want to test the application with 50 or 100 different users (With different accounts/credentials) connected to the server making API calls. Like 50 or 100 users are concurrently using the application. So I can ensure that the application is handling 50 users properly.
How can I do this kind of testing with a script?
Update: Most of the routes are hidden, they need #login_required.
I recommend you try Bees With Machine Guns. Its a python script that will launch micro EC2 instances and send many requests from these instances to your application. This will simulate a large surge in traffic for performance testing.
I heard about it from AWS training videos on CBT Nuggets. The instructor was effective using it to trigger auto scaling and load test his configuration.
Good luck.
You could try our little tool k6 also: https://github.com/loadimpact/k6
You script the behaviour of the virtual users using JavaScript, so it is quite easy to get 50 different users logging in with different credentials. Would look something like this (this code is going to need debugging though :)
import http from "k6/http";
let login_url = "http://ec2-instance-address.com/user/login";
let get_new_site_url = "http://ec2-instance-address.com/license/getNewSite";
let credentials = [
{ "account": "Account1", "username": "joe", "password": "secret" },
{ "account": "Account2", "username": "jane", "password": "verysecret" }
];
export default function() {
let session_id = doLogin();
let response = doGetNewSite(session_id);
let response_text = response["ResponseText"];
let new_site_id = response["NewSiteID"];
for (i = 0; i < loop_count; i++) {
// do heartbeat stuff?
}
}
function doLogin() {
let index = Math.floor(Math.random() * credentials.length);
let post_body = {
"AccountName": credentials[index]["account"],
"UserID": credentials[index]["username"],
"UserPassword": credentials[index]["password"]
};
let http_headers = { "Content-Type": "application/json" };
let res = http.post(login_url, JSON.stringify(post_body), { headers: http_headers });
check(res, {
"Response code is 200": (r) => r.status == 200,
"Login successful": (r) => JSON.parse(r.body).hasOwnProperty("SessionID")
});
return JSON.parse(res.body)["SessionID"];
}
function doGetNewSite(session_id) {
let http_headers = { "Content-Type": "application/json" };
let post_body = { "SessionID": session_id };
let res = http.post(get_new_site_url, JSON.strjngify(post_body), { headers: http_headers });
check(res, {
"Status code was 200": (r) => r.status == 200,
"Got response text": (r) => JSON.parse(r.body).hasOwnProperty("ResponseText"),
"Got new site id": (r) => JSON.parse(r.body).hasOwnProperty("NewSiteID")
});
return JSON.parse(res.body);
}

Resources