Why is my asynchronous (NodeJS-Python) WebSocket not connecting immediately? - javascript

This is a continuation of a question I had earlier, Using Socket IO and aiohttp for data transfer between node JS and Python, based on this tutorial, https://tutorialedge.net/python/python-socket-io-tutorial/.
I have an asynchronous tunnel that connects a Node JS client (send.js) and a python server (receive.py). Right now send.js outputs a random number and then sends it to the Python server (receive.py), which then sends the message back to the JS client.
The setup works, however, it takes a couple minutes for the server to start receiving data from send.js, and I do not know why.
The Node JS script will output data, but the server will not receive it for at least a couple minutes and even after it starts receiving data, it does not receive the data it did not get earlier, it will only receive the data from the moment the server and client can finally connect.
I am not sure if this has something to with the Python side, Node JS side, or something else.
I am using Node 8.16.1 and Python 3.7.3
The code is below:
send.js
const io = require('socket.io-client');
const socket = io('http://localhost:8080');
socket.on('reply', message => {
console.log('Got from server: ');
console.log(message);
});
function generateNumber() {
const n = Math.floor(Math.random() * 50);
return { number: n };
}
function sendMsg() {
const json = generateNumber();
console.log('Sending to server:');
console.log(json);
socket.emit('message', json);
}
function loop() {
const rand = Math.round(Math.random() * (3000 - 500)) + 500;
setTimeout(() => {
sendMsg();
loop();
}, rand);
}
socket.on('connect', () => {
console.log('Connected to server');
loop();
});
receive.py
from aiohttp import web
import socketio
# creates a new Async Socket IO Server
sio = socketio.AsyncServer()
# Creates a new Aiohttp Web Application
app = web.Application()
# Binds our Socket.IO server to our Web App
# instance
sio.attach(app)
# If we wanted to create a new websocket endpoint,
# use this decorator, passing in the name of the
# event we wish to listen out for
#sio.on('message')
async def print_message(sid, message):
# When we receive a new event of type
# 'message' through a socket.io connection
# we print the socket ID and the message
#print("Socket ID: " , sid)
print(message)
await sio.emit('reply', message)
# We kick off our server
if __name__ == '__main__':
web.run_app(app)
Let me know if more information is needed.

I don't know if you have to use the packages that you are using but here is my working version with ws package for node and asyncio and websockets packages for python. Have fun and nice question.
send.js
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8080')
console.log(ws)
function generateNumber() {
const n = Math.floor(Math.random() * 50);
return {
number: n
};
}
function sendMsg() {
const json = JSON.stringify(generateNumber());
console.log('Sending to server:');
console.log(json);
ws.send(json);
}
function loop() {
setTimeout(() => {
sendMsg();
loop();
}, 5000);
}
ws.on('open', function open() {
console.log('connect')
console.log(ws)
loop()
})
ws.on('message', function(data) {
console.log(data)
})
receive.py
import asyncio
import websockets
async def start(websocket, path):
print("connected")
while True:
data = await websocket.recv()
print(f"< {data}")
await websocket.send(data)
async def main():
server = await websockets.serve(start, 'localhost', 8080)
await server.wait_closed()
asyncio.run(main())

Related

Subscribing to Azure Pub Sub web service from react causes Unhandled Rejection (TypeError)

Based on the official documentation, i am able to get the subscribed messages. When i simply run the javascript code, it runs without any error.
const WebSocket = require('ws');
const { WebPubSubServiceClient } = require('#azure/web-pubsub');
async function main() {
const hub = "hub1";
let service = new WebPubSubServiceClient(process.env.WebPubSubConnectionString, hub);
let token = await service.getClientAccessToken();
let ws = new WebSocket(token.url);
ws.on('open', () => console.log('connected'));
ws.on('message', data => console.log('Message received: %s', data));
}
main();
But when i try to do this within React class's, componentDidMount() function, facing error.
import React from "react";
// == Azure WebPuSub
// import { WebPubSubServiceClient } from '#azure/web-pubsub';
// import { WebSocket } from 'ws';
const { WebPubSubServiceClient } = require('#azure/web-pubsub');
const WebSocket = require('ws');
class AzurePubSubTest extends React.Component {
constructor(_props, _context) {
super(_props, _context);
this.connectToPubSub = this.connectToPubSub.bind(this);
this.state = {
}
}
async componentDidMount() {
console.log("===Mounting....")
await this.connectToPubSub();
}
componentWillUnmount() {
console.log("Unmounting....")
}
async connectToPubSub() {
const hub = "hub1";
let endpoint;
// endpoint = process.env.WebPubSubConnectionString;
endpoint = "Endpoint=https://***check.webpubsub.azure.com;AccessKey=***;Version=1.0;"
// endpoint = "wss://***check.webpubsub.azure.com/client/hubs/Hub?access_token=***";
console.log("process.env.WebPubSubConnectionString");
console.log(endpoint);
let service = new WebPubSubServiceClient(endpoint, hub);
let token = await service.getClientAccessToken();
let ws = new WebSocket(token.url);
ws.on('open', () => console.log('connected'));
ws.on('message', data => console.log('Message received: %s', data));
}
render() {
const user = { username: "Check" };
let testMessages = [];
if (testMessages === undefined || testMessages === null) {
testMessages = [];
}
return (
<div>Testing....</div>
)
}
}
export default AzurePubSubTest;
× Unhandled Rejection (TypeError): Right-hand side of 'instanceof' is
not an object
Stacktrace 1
Stacktrace 2
Stacktrace 3
The issue here is with the Jsonwebtoken package which is used with the websocket.
Jsonwebtoken is predominantly build for NodeJS to be run on a web server so it doesn't fully work with the client-side rendering of the react apps
try installing the latest version of jsonwebtoken , otherwise the ideal way of working would be with an intermediary between the react app and azure pub sub.
One workaround with this approach would be with azure function with azure web pub sub input/output bindings. and then use a WebSocket in the react app to connect to the azure function.
Here you will need a HTTP trigger with the input bindings of the azure pub sub . This trigger will return the URL which you can use in web sockets of your react app.
function.json (for http trigger) :
{
"bindings":[
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req"
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "webPubSubConnection",
"name": "connection",
"hub": "notification",
"direction": "in"
}
]
}
Here I am sending the message using a time trigger and in a simple HTML file I created a WebSocket the html file which is served using different HTTP trigger. Thus after every interval of time I will get messages

Serverless WebSockets - No method found matching route #connections/* for http method POST

I'm using Serverless Framework to host my WebSocket, which has the typical $connect, $disconnect, $default, etc methods that updates my connections db:
case '$connect':
await dynamoDb.put({
TableName: process.env.CONNECTIONS_TABLE,
Item: {
connectionId,
// Expire the connection an hour later. This is optional, but recommended.
// You will have to decide how often to time out and/or refresh the ttl.
ttl: parseInt((Date.now() / 1000) + 3600)
}
}).promise();
My WebSocket setup is:
WebSocket URL: wss://1111111111.execute-api.ap-southeast-2.amazonaws.com/dev/
Connection URL: https://1111111111.execute-api.ap-southeast-2.amazonaws.com/dev/#connections
My HTTP setup is:
Invoke API at: https://222222222.execute-api.ap-southeast-2.amazonaws.com/dev/
I have a broadcast function which I am using to send data to the connections, which I am invoking with:
sls invoke --function broadcast --data '{ \"body\": \"Hello from server\" }'
The source sends a message to each connection, as provided in the params of the request:
async function sendMessage(connectionId, body) {
try {
await apig.postToConnection({
ConnectionId: connectionId,
Data: body
}).promise();
} catch (err) {
// Ignore if connection no longer exists
if(err.statusCode !== 400 && err.statusCode !== 410) {
throw err;
}
}
}
async function getAllConnections(ExclusiveStartKey) {
const { Items, LastEvaluatedKey } = await dynamoDb.scan({
TableName: process.env.CONNECTIONS_TABLE,
AttributesToGet: [ 'connectionId' ],
ExclusiveStartKey
}).promise();
const connections = Items.map(({ connectionId }) => connectionId);
if(LastEvaluatedKey) {
connections.push(...await getAllConnections(LastEvaluatedKey));
}
return connections;
}
module.exports.handler = async function(event, context) {
const { body } = event;
const connections = await getAllConnections();
await Promise.all(
connections.map(connectionId => sendMessage(connectionId, body))
);
}
A connection can be established (I can connect to the WebSocket), however when I try to invoke this function I am receiving the error:
No method found matching route #connections/ZE4SDfSJSwMCJ4g%3D for http method POST.
The ZE4SDfSJSwMCJ4g is my connectionId, which exists in my database. I am unsure if this routing issue has to do with my HTTP API and my WebSocket API being pointed at different API Gateway URL?
I appreciate the help!
Make sure that the endpoint in your API Gateway management configuration is the same as your ws endpoint.
const agma = new AWS.ApiGatewayManagementApi({
apiVersion: AGMA_VERSION,
endpoint: WS_ENDPOINT // 1111111111.execute-api.ap-southeast-2.amazonaws.com/dev
})
I too had this issue with websockets and google cloud functions. I think it's because the web socket server is put to sleep and doesn't actively listen because it's serverless.
Functions are configured to wake up on http requests (ports 80/443) and go back to sleep, so they need to be specially configured to wake up to requests on web socket port.
This seems possible using a serverless websockets plugin https://github.com/serverless/serverless-websockets-plugin there is a how to article at https://www.serverless.com/blog/api-gateway-websockets-example

Returning a value from async function to outer function Node js [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I am fairly new to the whole asynchronous world. Had to start for implementing Twilio video call through node.
I have been trying to call this server side function which then calls another asynchronous function that returns promise. The then portion works fine and I am able to see output in console. But when I try to call another function in then which has an ajax call to the main application to save some data in database.
Neither am I able to send the returned value in response.send() nor am I able to call a function in then.
Please help!
Server Side - index.js
'use strict';
/**
* Load Twilio configuration from .env config file - the following environment
* variables should be set:
* process.env.TWILIO_ACCOUNT_SID
* process.env.TWILIO_API_KEY
* process.env.TWILIO_API_SECRET
*/
require('dotenv').load();
const express = require('express');
const http = require('http');
const path = require('path');
const { jwt: { AccessToken } } = require('twilio');
const Twilio = require('twilio');
const jquery = require( 'jQuery');
const cors = require('cors');
const VideoGrant = AccessToken.VideoGrant;
// Max. period that a Participant is allowed to be in a Room (currently 14400 seconds or 4 hours)
const MAX_ALLOWED_SESSION_DURATION = 14400;
// Create Express webapp.
const app = express();
// Set up the path for the HealthAssure.
const startPath = path.join(__dirname, '../HAVideoConsultation/public');
app.use('/HAVideoConsultation', express.static(startPath));
/**
* Default to the application.
*/
app.get('/', (request, response) => {
response.redirect('/HAVideoConsultation');
});
/**
* Generate an Access Token for a chat application user - it generates a random
* username for the client requesting a token, and takes a device ID as a query
* parameter.
*/
app.get('/token', function(request, response) {
const { identity } = request.query;
// Create an access token which we will sign and return to the client,
// containing the grant we just created.
const token = new AccessToken(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_API_KEY,
process.env.TWILIO_API_SECRET,
{ ttl: MAX_ALLOWED_SESSION_DURATION }
);
// Assign the generated identity to the token.
token.identity = identity;
// Grant the access token Twilio Video capabilities.
const grant = new VideoGrant();
token.addGrant(grant);
// Serialize the token to a JWT string.
response.send(token.toJwt());
});
function pushCompositionId(compositionId){
console.log(compositionId);
jquery.ajax({
url:'http://localhost:58674/ABC/XYZ',
type:'GET',
data: {CompositionId:compositionId},
cors: true,
success:function(result){
Console.log('Composition Id pushed successfully.');
},
error:function(err){
console.log(err);
return false;
}
});
}
app.get('/Composition',function(request,response){
const client = new Twilio(process.env.TWILIO_API_KEY,process.env.TWILIO_API_SECRET, {accountSid: process.env.TWILIO_ACCOUNT_SID});
const cid = null;
client.video.compositions
.create({
roomSid: request.query.roomSid,
audioSources: '*',
videoLayout: {
grid : {
video_sources: ['*']
}
},
format: 'mp4'
}).then(composition =>{
console.log("Created Composition with SID=" + composition.sid); // This works properly
cid=composition.sid;
// pushCompositionId(composition.sid); // I want to call this function here
});
response.send(cid); // This does not return proper value
});
// Create http server and run it.
const server = http.createServer(app);
const port = process.env.PORT || 3000;
server.listen(port, function() {
console.log('Express server running on *:' + port);
});
Client side
async function GenerateCompositionId(roomsid){
const compositionid = await fetch(`/Composition?roomSid=${roomsid}`);
}
Server Side function I want to call in then after Composition.sid is generated. If I put this in a try block it gives me error jquery.ajax is not a function. I have included it require and another ajax function on client side is working fine. Why does this not?
function pushCompositionId(compositionId){
jquery.ajax({
url:'http://localhost:58674/ABC/XYZ',
type:'GET',
data: {CompositionId:compositionId},
cors: true,
success:function(result){
Console.log('Composition Id pushed successfully.');
},
error:function(err){
console.log(err);
return false;
}
});
}
In order to send the response when the async calls are done the server-side must be:
var jsdom = require("jsdom");
const { JSDOM } = jsdom;
const { window } = new JSDOM();
const { document } = (new JSDOM('')).window;
global.document = document;
var $ = jQuery = require('jquery')(window);
function pushCompositionId(compositionId,response){
console.log(compositionId);
jquery.ajax({
url:'http://localhost:58674/ABC/XYZ',
type:'GET',
data: {CompositionId:compositionId},
cors: true,
success:function(result){
Console.log('Composition Id pushed successfully.');
response.send(cid); // This does not return proper value
},
error:function(err){
console.log(err);
response.send(false);
}
});
}
app.get('/Composition',function(request,response){
const client = new Twilio(process.env.TWILIO_API_KEY,process.env.TWILIO_API_SECRET, {accountSid: process.env.TWILIO_ACCOUNT_SID});
const cid = null;
client.video.compositions
.create({
roomSid: request.query.roomSid,
audioSources: '*',
videoLayout: {
grid : {
video_sources: ['*']
}
},
format: 'mp4'
}).then(composition =>{
console.log("Created Composition with SID=" + composition.sid); // This works properly
cid=composition.sid;
pushCompositionId(composition.sid,response); // I want to call this function here
});
});
Otherwise you can use await/async to return a sync-like result

QueueEvents don't trigger with BullMQ using Heroku Redis

I'm trying to implement a queue in NodeJS using BullMQ but i have some issues in production when trying to use a remote Redis (Heroku Redis or Redis Cloud).
In local, everything work well but when i try to use a REDIS_URL, a job is created but events doesn't work.
Here is the code:
// test_job.js
import { Queue, Worker, QueueEvents } from "bullmq";
import IORedis from "ioredis";
import Dotenv from "dotenv";
Dotenv.config();
// Good
const connection = new IORedis(process.env.REDIS_URL || 6379);
// Good
const queue = new Queue("Paint", { connection });
// Good
const worker = new Worker(
"Paint",
async job => {
if (job.name === "cars") {
console.log(job.data.color);
}
},
{ connection }
);
/**
* BUG HERE: Events work in local but not when using a remote Redis (REDIS_URL)
*/
const queueEvents = new QueueEvents("Paint");
queueEvents.on("completed", jobId => {
console.log("done painting");
});
queue.add("cars", { color: "blue" });
const queueEvents = new QueueEvents("Paint", { connection: connection.duplicate() });
https://github.com/taskforcesh/bullmq/issues/173

Sharing SFTP Connection in Azure Functions

I have an azure function that sends a file to an SFTP server. It may be called multiple times so to save time connecting to the SFTP server I would like to reuse the SFTP connection.
const Client = require("ssh2-sftp-client");
const sftp = new Client();
let sftpConnected = false;
const sendToSFTP = async (data, location, context) => {
await setConn(context)
try{
await sftp.put(data, location);
}catch(err){
context.log.error('sftp put error: ' + err);
}
}
const setConn = async (context) => {
if (sftpConnected) return
try{
await sftp.connect({
host: 'myserver',
username: 'user',
passphrase: 'pwd',
});
sftpConnected = true;
}catch(err){
context.log.error('sftp connect error: ' + err);
}
}
sftp.on('close', () => {
sftpConnected = false;
sftp.end();
});
sftp.on('end', () => {
sftpConnected = false;
sftp.end();
});
exports.sendToSFTP = sendToSFTP;
This works when testing locally but when deployed to Azure the close and end listeners do not seem to be called when the Azure function goes idle. When I try to use the sftp connection after a period of time there is no connection there and the call to the sftp server times out.
Has anyone solved this issue?

Categories