I have a node js API built with express. In one of my http endpoints, I would like to accept the request and send it over websockets to another server, and then return a reply. I'm using the ws library. The problem is that the websockets communication is not synchronous, so I don't know how to return an answer to the API client. Anyway, this is what I tried but it's not complete:
const express = require('express');
const app = express();
const WebSocket = require('ws');
const uuid = require('uuid');
const bodyParser = require('body-parser');
const ws = new WebSocket('ws://localhost:7465/');
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ extended: false }));
ws.on('open', function open() {
console.log('connected');
});
ws.on('close', function close() {
console.log('disconnected');
});
ws.on('message', function incoming(data) {
console.log('Got data from server:');
console.log(data);
});
app.post('/my-http-endpoint', function (req, res) {
var payload = JSON.stringify({ body: req.body, requestID: uuid.v4() });
ws.send(Buffer.from(payload));
// How to send a reply?
})
app.listen(1337);
You will need to introduce message IDs. Generate them by counting up a number.
Add the following dictionary to your code:
let nextMessageId = 1;
const responseCallbacks = {};
When you send something (in the app.post callback in your case), do the following:
const messageId = obtainFreeMessageId(); // see below
ws.send(Buffer.from({ messageId, payload }));
responseCallbacks[messageId] = function(data) {
// make use of the response message data here
};
On message do the following (the response must contain messageId as well):
const messageId = message.messageId; // message has a message ID
const responseCallback = responseCallbacks[messageId];
if(responseCallback) {
responseCallback(message.data); // message contains your data
delete responseCallbacks[messageId];
}
Helper function for message ID generation which helps prevent possible overflow:
const MaximumMessageId = 0xFFFFFFFFFFFF; // 48-bit integer (64-bit is too much for javascript, 32-bit is a little bit too stingy)
function obtainFreeMessageId() {
const messageId = nextMessageId;
nextMessageId++;
if(nextMessageId > MaximumMessageId) {
nextMessageId = 1;
}
return messageId;
}
Consider setting a timeout which calls the response callback after a certain amount of time if you want to make sure the request comes to an end. Clear the timeout in the response callback.
Related
I want to read data from serial port and get from data when reqested
Here is my code
const http = require('http');
const hostname = 'localhost';
const { SerialPort } = require('serialport')
const { ReadlineParser } = require('#serialport/parser-readline')
const { io } = require('socket.io');
let express = require('express')
const serialPort = new SerialPort({
path: 'COM4',
baudRate: 9600 ,
})
const parser = serialPort.pipe(new ReadlineParser({ delimiter: '\r\n' }))
let app = express();
var port = 8080;
const server = http.createServer(app);
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
app.get('/get_data', function(req, res) {
parser.on('data', function(data) {
res.json({'weight': data});
});
});
When i am try to get data i got ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client
I want serial port data when requested from localhost:8080/get_data anyone can help ?
Your data event from parser is probably firing more than once, which means you would be calling res.json more than once. As you can see in the express api documentation, res.json sets the content-type header...thus you can only call it once per request. Hence the error.
What I think would normally be done in this kind of situation is to set up a queuing system. A simple version might be done using an array, although if you were using this in a production server it might be better to use a proper message queuing system (e.g. rabbitMQ, kafka, AWS SQS, etc).
Here's an example of how you might use an array:
const queue = [];
parser.on('data', function(data) {
// push new data onto end of queue (array)
queue.push(data);
});
app.get('/get_data', function(req, res) {
if (req.params.getFullQueue === 1) {
// empty complete contents of current queue,
// sent to client as an array of { weight: x } objects
const data = queue.splice(0, queue.length)
.map(x => ({ weight: x }));
res.json(data);
} else {
// get oldest enqueued item, send it only
res.json({ weight: queue.shift() });
}
});
The if/else in the app.get is meant to illustrate these two options, depending on which you wanted to use. In production you'd probably want to implement pagination, or maybe even a websocket or EventSource so that data could be pushed as it became available.
i want to send data to client side with node.js every 5s. it is a coordinate data i want to send to do realtime location. i use mqtt client , firebase realtime databas to store and retrieve data and flutter to show live location. my problem, is how can i send every 5s data from node.js to client
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
var http = require('http');
const port = process.argv.slice(2)[0];
const app = express();
app.use(bodyParser.json());
const mqtt = require('mqtt')
const client = mqtt.connect('mqtt://broker.hivemq.com')
var firebase = require('firebase');
const server = http.Server(app)
//fetching data from firebase realtime database
function readDatas() {
let coordinateData = new Array()
loraRef.on('value', (snapshot) => {
snapshot.forEach(childSnapshot => {
coordinateData.push(childSnapshot.val().message);
// console.log('ok');
},
);
});
//inserting data into firebase realtime database
function insert_message(topic, message, packet) {
loraRef.push({
message
})
}
//inserting data from mqtt into firebase realtime database
function mqtt_messsageReceived(topic, message, packet) {
if(message){
let messageObject = JSON.parse(message)
insert_message(topic, messageObject, packet)
}
};
client.on('message', mqtt_messsageReceived)
//sending data to http client
let dataCoordinate = readDatas()
app.get('/coordinate', async(req, res) => {
console.log('Returning coordinate list');
if(dataCoordinate.length !==0){
res.send(dataCoordinate[0]);
//res.sendStatus(dataCoordinate)
console.log(readDatas()[0]);
}
});
I'm currently using Glitch's(Glitch.com) node.js to connect Dialogflow to code and I'm running into a problem. As you can see below; I have two intents I'm trying to pass values to, characterHandler and openHandler.
Now the weird thing is that it does execute the web hook correctly if I trigger the intent on Dialogflow corresponding to "characterHandler", but it returns "UnhandledPromiseRejectionWarning: Error: no matching intent handler for: null" in the console and fails while triggering "openHandler" and I have no clue why.
Does anyone know what I'm doing wrong?
'use strict';
process.env.DEBUG = 'actions-on-google:*';
const express = require('express');
const bodyParser = require('body-parser');
const request = require("request");
const { DialogflowApp } = require('actions-on-google');
const Map = require('es6-map');
const app = express();
app.use(bodyParser.json());
let characters = ['The Pied Piper', 'Red Riding Hood', 'The Big Bad Wolf'];
// [START Action]
app.post('/', function (request, response) {
const assistant = new DialogflowApp({request, response});
console.log('Request headers: ' + JSON.stringify(request.headers));
console.log('Request body: ' + JSON.stringify(request.body));
const CHARACTERS = 'story.characters';
const OPENINGTIMES = 'openingTimes';
function characterHandler (assistant) {
let responseText = "How about";
responseText = characters[Math.floor(Math.random() * characters.length)];
assistant.tell(responseText);
}
function openHandler (assistant) {
assistant.tell('This attraction is currently full');
}
const actionMap = new Map();
actionMap.set(CHARACTERS, characterHandler);
actionMap.set(OPENINGTIMES, openHandler);
assistant.handleRequest(actionMap);
});
// [END Action]
// Renders the homepage
app.get('/', function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('');
res.end();
});
if (module === require.main) {
// [START server]
// Start the server
let server = app.listen(process.env.PORT || 3000, function () {
let port = server.address().port;
console.log('App listening on port %s', port);
});
// [END server]
}
module.exports = app;
Your open handler function is mapped to 'openingTimes'. Make sure that exactly matches the intent name and make sure that the intent was actually saved correctly.
I am wondering about how it might be possible to deploy a Node.js app on Azure Functions.
Basically, I have a function setup and running a basic hello world http example that looks like:
module.exports = function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
context.res = {
// status: 200, /* Defaults to 200 */
body: "Hello " + req.params.name
};
context.done();
};
The app I am trying to deploy into a function is a simple moc client that uses swagger (basically takes a request and returns some xml). The app.js looks like:
const SwaggerExpress = require('swagger-express-mw');
const app = require('express')();
const compression = require('compression');
const configSwagger = {
appRoot: __dirname, // required config
};
SwaggerExpress.create(configSwagger, (err, swaggerExpress) => {
if (err) {
throw err;
}
// install middleware
swaggerExpress.register(app);
// server configuration
const serverPort = process.env.PORT || 3001;
app.listen(serverPort, () => {
//logger.info('Listening on port %s', serverPort);
});
app.use(compression());
});
module.exports = app; // for testing
The thing I am not sure about is how to handle module.exports = app when modeul.exports is used to establish the function (i.e. module.exports = function (context, req))
You can try to use azure-function-express to enable your swagger middleware.
Note that certain middleware will not function correctly (for example, body-parser). This is because the functions req is not a stream - it is injected into the function with a 'body' property already populated.
I'm new to node and got stuck with handling multiple async tasks.
Except from node, I've got another server (S1) which doesn't return data immediately to requests, it can returns multiple types of data and also can send notifications without requesting them specifically, so node have to listen to data from it , parse it and act accordingly.
The connection to this server (S1) is done by using:
S1 = net.createConnection({'host':S1Host, 'port': S1Port});
And node listens to data with:
S1.on('data', function(data){
S1DataParse(data);
});
I have to route the correct data (after parsing it) to a specific POST request.
app.post('/GetFooFromS1', function(req, res){
// Send request to S1
S1.write({'type':'foo'});
// If got the correct data sometime in the future, send response to the browser
res.setHeader('Content-Type', 'application/json');
res.json({'status':'success', 'value':S1FooData});
});
I tried to use the async module for that, but with no success.
What I was trying to do:
var asyncTasks = [];
app.post('/GetFooFromS1', function(req, res){
asyncTasks.push(function(callback){
// Send request to S1
S1.write({'type':'foo'});
});
async.parallel(asyncTasks, function(response){
res.setHeader('Content-Type', 'application/json');
res.json({'status':'success', 'value':response});
});
});
and another task in S1DataParse:
function S1DataParse(){
if(data.type='foo'){
asyncTasks.push(function(callback){
callback(data);
});
}
}
But, of course, the second task never added to the asyncTasks array. I really got stuck with that.
Can you please help me with that?
Thanks
-=-=-=- Edit -=-=-=-
Eventually, I came accross with events and EventEmitter().
From the POST request I call the function that sends requests to the data server (DataServerClientGet).
In this function I register a listener which will get the future data.
eventEmitter.on('getData', returnDataServerData);
It all works great except for one thing. Whenever I refresh the page or add other POST requests, I get an error:
Error: Can't set headers after they are sent.
It would be great if I solve this problem. Help me, please.
Thanks ;)
The whole code looks like this:
var express = require('express');
var app = express();
var http = require('http').Server(app);
var bodyParser = require('body-parser')
var net = require('net');
var events = require('events');
var dataServerHost = '127.0.0.1';
var dataServerPort = 12345;
var dataServerClient;
var logMsg;
var eventEmitter = new events.EventEmitter();
/*******************************************/
// Init
/*******************************************/
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(express.static(__dirname + '/public'));
/*******************************************/
// Connect to the data server
/*******************************************/
DataServerConnect();
/*******************************************/
// Open listener on port 3000 (to browser)
/*******************************************/
http.listen(3000, function(){
logMsg = 'listening on *:3000';
console.log(logMsg);
});
/*******************************************/
// Routing
/*******************************************/
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
app.post('/GetDataFoo', function(req, res){
var msg;
var size;
msg ='\n{"Type":"Query", "SubType":"GetDataFoo","SearchFilter":""}';
size = msg.length;
logMsg = 'Client to DataServer: GetDataFoo';
console.log(logMsg);
DataServerClientGet('GetDataFoo', size, msg, res);
});
/*******************************************/
// Functions
/*******************************************/
function DataServerConnect(){
dataServerClient = net.createConnection({'host':dataServerHost, 'port': dataServerPort}, function(){
logMsg = 'Connected to DataServer ['+dataServerHost+':'+dataServerPort+']';
console.log(logMsg);
});
dataServerClient.on('data', function(data){
logMsg = 'DataServerData>>>\n'+data.toString()+'DataServerData<<<';
console.log(logMsg);
DataServerDataParse(data.toString());
});
dataServerClient.on('end', function(){
logMsg = 'Disconnected from DataServer';
console.log(logMsg);
});
}
function DataServerClientGet(type, size, msg, res){
dataServerClient.write('Type: Json\nSize: '+size+'\n\n'+msg, function(err){
var returnDataServerData = function returnDataServerData(results){
res.setHeader('Content-Type', 'application/json');
res.json({'status':'success', 'value':results});
}
eventEmitter.on('getData', returnDataServerData);
}
function DataServerDataParse(json){
if(json.Type=='GetDataFoo')
{
var MessageList = json.MessageList;
eventEmitter.emit('getData', MessageList);
}
}
-=-=-=- Edit -=-=-=-
The Error: Can't set headers after they are sent. caused by adding the same listener of the same type each time the DataServerClientGet was called and the res was sending multiple times.
I solved this one by adding: removeListener(event, listener)
right after the res, inside the function. Anyway, I think it's wrong and can cause problems if there will be multiple calling to DataServerClientGet with the same type etc.
There is a optional callback parameter that you can pass to write function(docs), something like :
S1.write({'type':'foo'},function(err){
if(err){
//Handle error
}else{
res.setHeader('Content-Type', 'application/json');
res.json({'status':'success', 'value':response});
}
})
This can work with post route , but in your 'data' listener ,you cant send data from server to client when there is not connection initialized by client (it is not bidireccional ) if you want bidireccional behavior you can check socket.io