Nodejs wait for socket and then respond to http request - javascript

I have a NodeJS code where, basically this is what happens:
HTTP request (app.get) -> Send a request to a low level socket(using net.Socket()) -> Get response from socket -> res.send(response from socket)
This doesn't work because the net.Socket() uses async functions and events (client.on("data", callback)).
I tried something like this:
app.get("/", function(req, res){
client.connect("localhost", 420, function(){
client.write("example data");
});
client.on("data", function(data){
client.destroy();
res.send(data);
});
});
But it doesn't work because it says I am re-sending the headers (the res object won't change since the function is an event, not a sync function).
Any ideas? Or a library for sync socket requests? I have tried the following:
Synket
sync-socket
netlinkwrapper
And they don't work.
Edit: I am trying something like this:
async function sendData(client, res){
client.on('data', function(data){
console.log("Got data!");
res.send(""+data);
res.end();
console.log("Sent data!");
client.destroy();
console.log("Killed connection");
return;
});
}
app.get("/", function(req, res){
var body = req.query;
client.connect(420, "localhost", function(){
client.write("some random data");
console.log("Connected & Data sent!");
sendData(client, res);
});
});
It works the first time I try to access the page, but the second time the app crashes and I get this error:
_http_outgoing.js:489
throw new Error('Can\'t set headers after they are sent.');
^
Error: Can't set headers after they are sent.

client triggles data event multiple times.
`app.get("/", function(req, res){
client.connect("localhost", 420, function(){
client.write("example data");
});
client.pipe(res);
});
});
`

It turns out that, each time a request comes to that endpoint, a new subscription has been registered by calling the client.on('eventName', cb). So, calls starting from the second one will trigger multiples of those registrations and will cause that header error.
So, a workaround for that:
socket.on('event', (packet) => {
socket.removeAllListeners('event')
res.json({ packet });
});
That would do the trick but I'm not sure if it's a bad practice to continuously add/remove the same event.
Edit
Found a better way. once will ensure that registered event only will run once, just as we want:
socket.once('event', (packet) => {
res.json({ packet });
});

Related

Node.JS and Express res.redirect() not enabling new webpage

I'm trying to save a variable to a text file, but if the variable isn't found when using spotifyApi.clientCredentialsGrant(), then I want my server to redirect to app.get('/error', function(req, res) {}); which displays a different webpage, but it's returning the error:
(node:11484) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
How can I get around this error to display the webpage error.html?
I don't have access to EJS or window.location because it conflicts with other files and it's a node.js program, respectively.
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, '/public', 'homepage.html'));
try {
spotifyApi.clientCredentialsGrant()
.then(function (data) {
// Save the access token so that it's used in future calls
client_cred_access_token = data.body['access_token'];
console.log(client_cred_access_token);
console.log('Client Credentials Success!');
}, function (err) {
console.log('Something went wrong when retrieving an access token', err.message);
throw err;
});
fs.writeFile("./public/client_cred_token.txt", '', function (err) {
console.log('Clearing previous access token');
});
fs.writeFile("./public/client_cred_token.txt", client_cred_access_token, function (err) {
if (err) return console.log(err);
});
fs.readFile('./public/client_cred_token.txt', function (err, data) {
if (err) throw err;
console.log("Saved Client Credentials as: %s", data)
});
}
catch (err) {
res.redirect('/error');
}
});
Key takeaway from the accepted answer is to not send any HTML/files to the server until it's confirmed which one is needed.
You are calling res.sendFile() first and then if you later get an error, you are also calling res.redirect('/error') which means you'll be trying to send two responses to one http request which triggers the error you see. You can't do that.
The solution is to call res.sendFile() at the end of all your other operations so you can then call it when successful and call res.redirect() when there's an error and thus only call one or the other.
In a difference from the other answer here, I've shown you how to code this properly using asynchronous file I/O so the design could be used in a real server designed to serve the needs of more than one user.
const fsp = require('fs').promises;
app.get('/', async function (req, res) {
try {
let data = await spotifyApi.clientCredentialsGrant();
// Save the access token so that it's used in future calls
client_cred_access_token = data.body['access_token'];
console.log(client_cred_access_token);
console.log('Client Credentials Success!');
await fsp.writeFile("./public/client_cred_token.txt", client_cred_access_token);
let writtenData = await fsp.readFile('./public/client_cred_token.txt');
console.log("Saved Client Credentials as: %s", writtenData);
res.sendFile(path.join(__dirname, '/public', 'homepage.html'));
} catch (err) {
console.log(err);
res.redirect('/error');
}
});
app.get('/', function (req, res) {
try {
spotifyApi.clientCredentialsGrant().then(function (data) {
// Save the access token so that it's used in future calls
let client_cred_access_token = data.body['access_token'];
console.log(client_cred_access_token);
console.log('Client Credentials Success!');
// truncate token file
fs.truncateSync("./public/client_cred_token.txt");
// write token to file
fs.writeFileSync("./public/client_cred_token.txt", client_cred_access_token);
// read token from file again
// NOTE: you could use `client_cred_access_token` here
let data = fs.readFileSync('./public/client_cred_token.txt');
console.log("Saved Client Credentials as: %s", data)
// send homepage to client when no error is thrown
res.sendFile(path.join(__dirname, '/public', 'homepage.html'));
}, function (err) {
console.log('Something went wrong when retrieving an access token', err.message);
throw err;
});
} catch (err) {
res.redirect('/error');
}
});
I swapped all asynchron file opreations with the syncron one.
They throw an error and you dont have to deal with callback chain/flow.
Also i moved the sendFile(...) at the botom in the try block, so when a error is thrown from any syncrhonus function call the sendFile is not reached, and your redirect can be sent to the client.
Otherwise you would send the homepage.html to the client, with all headers, and a redirect is not possible.

Cannot GET / DELETE Express.js

I have this script with which I'm trying to POST, GET and DELETE some stuff.
When I try POST or GET, the right messages are logged, but when I try DELETE, I get the following error:
Cannot GET /del_user
The URL I'm using is http://127.0.0.1:8081/del_user
What can be wrong in here?
var express = require('express');
var app = express();
// This responds with "Hello World" on the homepage
app.get('/', function (req, res) {
console.log("Got a GET request for the homepage");
res.send('Hello GET');
})
// This responds a POST request for the homepage
app.post('/', function (req, res) {
console.log("Got a POST request for the homepage");
res.send('Hello POST');
})
// This responds a DELETE request for the /del_user page.
app.delete('/del_user', function (req, res) {
console.log("Got a DELETE request for /del_user");
res.send('Hello DELETE');
})
// This responds a GET request for the /list_user page.
app.get('/list_user', function (req, res) {
console.log("Got a GET request for /list_user");
res.send('Page Listing');
})
// This responds a GET request for abcd, abxcd, ab123cd, and so on
app.get('/ab*cd', function(req, res) {
console.log("Got a GET request for /ab*cd");
res.send('Page Pattern Match');
})
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
})
I solved it by changing the app.delete to app.get and then placing the required remove statement inside the app.get. Something like this :-
app.get('/delete/:userId', (req, res) => {
Users.remove({ _id: req.params.userId }, (error, posts) => {
if (error) {
console.warn(error);
}
else {
data = posts
res.render("delete", {"data": data})
}
});
});
In your code you're binding the /del_user URL to the HTTP DELETE method.
So all you need to do is specify the DELETE method in your application or in Postman.
If you're not using it, it's an App in Google Chrome and you might want to download it, it makes your life a LOT easier ;)
Also, since the HTTP method is already declared to be DELETE, there is no need to specify it in the URL.
This is part of the RESTful working.
If you are using AJAX to try your code, you need to specify the method, which is delete.
$.ajax({
url: "http://127.0.0.1:8081/del_user",
type: "DELETE"
});

Secure POST request from NodeJS to Node/Express hangs

I'm using the following to send POST data to a secure nodejs server:
File: main.js
var strdata = JSON.stringify({"data":"thisdata"});
var options = {
host: '192.168.1.63',
port: 3001,
path: '/saveconfig',
method: 'POST',
rejectUnauthorized: false,
requestCert: true,
agent: false,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(strdata)
}
};
var req = https.request(options, function(res) {
console.log('STATUS: ' + res.statusCode);
console.log('HEADERS: ' + JSON.stringify(res.headers));
res.setEncoding('utf8');
res.on('data', function (chunk) {
console.log('BODY: ' + chunk);
});
});
console.log(req.write(strdata));
console.log(req.end());
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
req.on('finish', function() {
console.log("finished request");
});
In an otherwise functional expressjs server, these are the appropriate snippets:
File: app.js
app.post('/saveconfig', function() {
data.saveconfig; console.log("received request"); } );
app.get('/getconfig', data.getconfig);
File: data.js
exports.saveconfig = function(req, res) {
console.log("saveing config");
res.send(200);
res.end();
};
exports.getconfig = function(req, res) {
res.send("get OK");
}
With app.js running on the server (Ubuntu), I run main.js from the client (Windows 7). req.write and req.end execute and "finished request" logs to the console, but the request callback never fires.
On the server in app.js, the app.post event fires and logs "received request" to the console. But "saving config" never logs to the console. Only after I kill (^C) main.js, express then logs to the console "POST /saveconfig".
I know I'm missing something simple, but I've read dozens of coding examples and you can likely gather from my snippet, I've tried everything I can find or think of. I'd guess the request isn't finishing, but I don't know why. What is missing to get "exports.saveconfig" to fire?
additional information
The answer posted below fixed my problem. Because I'm new to stackoverflow, I can't post my own answer, but here's the rest of the story...
I appreciate your help. Being still new to JavaScript, I found I can learn a lot about an object by converting it to string. I was originally attempting to convert the req parameter to a string using a custom function. I just discovered it was apparently running into an endless loop after using JSON.stringify instead.
The code looked something like this:
exports.saveconfig = function (db) {
return function(req, res) {
console.log("saving config");
console.log(mymodule.serialize(req));
res.end("OK");
console.log(req.body);
};
};
I would have thought the above code should have logged the following to the console- even if the serialize method was in an endless loop:
POST /saveconfig
saving config
[nothing because of the endless loop]
Instead I got:
saving config
connections property is deprecated. Use getConnections() method
Being new to JavaScript, I assumed something was wrong with the request, the server, or some plumbing in-between. Of course, then the debugging code I added (along with my ignorance of JS) compounded the problem.
Changing
app.post('/saveconfig', function() {
data.saveconfig; console.log("received request"); } );
to
app.post('/saveconfig', datarts.saveconfig);
and removing the endless loop fixed the problem.
The problem is in you're app.js. You use data.saveConfig inside your callback without calling it. The right way would be
app.post('/saveconfig', function(req,res) {
data.saveconfig(req, res);
console.log("received request");
});
app.get('/getconfig', data.getconfig);
or (I assume the console.log is just for debugging purposes):
app.post('/saveconfig', data.saveconfig);
app.get('/getconfig', data.getconfig);
You could do your console.log() inside your data.saveconfig method if you want to go with the second example.

Sails JS Publish Update Not working

Writing a simple call tracker with sails js and twilio node but publishUpdate doesn't seem to be sending out messages to my frontend clients. Model.publish IS working but Model.publishUpdate is not. Am I doing any thing obviously wrong or is there something I'm misisng?
Here is my code.
In my primary controller, I have a specific route to subscribe clients to my Model
in MainController.js
calls: function(req,res){
Calls.find(function foundCalls(err, calls) {
if (err) return next(err);
// subscribe this socket to the Calls classroom
Calls.subscribe(req.socket);
// subscribe this socket to the Calls instance rooms
Calls.subscribe(req.socket, calls);
// This will avoid a warning from the socket for trying to render
// html over the socket.
res.send(calls,200);
});
},
In my app.js file I have
socket.on('connect', function socketConnected() {
socket.get('/main/calls', function(response){console.log(response)});
socket.on('message', function(message) {
console.log(message);
});
});
In another controller I have this:
CallController.js
takecall: function(req,res) {
Calls.findOneByCallSid(req.body.CallSid).done(function(err, thiscall) {
if(err){
return next(err);
} else {
// 1a. Save additional parameters
thiscall.DialCallDuration=req.body.DialCallDuration
thiscall.DialCallSid=req.body.DialCallSid
thiscall.RecordingUrl=req.body.RecordingUrl
thiscall.DialCallStatus=req.body.DialCallStatus
thiscall.RecordingDuration=req.body.RecordingDuration
thiscall.RecordingSid=req.body.RecordingSid
thiscall.save(function(err,call) {
if (err) return next(err);
console.log(call.id);
Calls.publishUpdate(call.id,{message:'hello'});
});
// 3. Display twiml that tells twilio we are done with call workflow
var twilio = require('twilio');
var resp = new twilio.TwimlResponse();
res.send(resp.toString(), 200);
}
});
}

Ajax post response from express js keeps throwing error

I am having a real hard time standing up bidirectional data transfer from a html page to my node.js application and then back to the html page.
I'm pretty sure I'm all over the correct solution, but I'm just not getting it to work.
I'm using the following for my node.js application:
var express = require('/usr/lib/node_modules/express');
var app = express();
app.use(function(err, req, res, next){
console.error(err.stack);
res.send(500, 'Something broke!');
});
app.use(express.bodyParser());
app.post('/namegame', function(req, res)
{
console.log('Got request name: ' + req.body.name);
setTimeout(function()
{
var newName = "New Name-O";
res.send({name: newName});
}, 2000);
}
);
app.listen(8088, function() { console.log("Server is up and running"); });
The following is my html page:
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.js"></script>
<script type="text/javascript">
// wait for the DOM to be loaded
$(document).ready(function()
{
alert("Got here!");
function DisplayName(response)
{
alert(response.newName);
}
$("#NameGameIt").click(function(event)
{
alert("How about now?");
//event.PreventDefault();
var nameSubmitted = document.getElementById("name");
//var nameSubmitted = "help!!";
alert("Name: " + nameSubmitted.value);
$.ajax({
type: "POST",
url: "http://127.0.0.1:8088/namegame",
data: {name: nameSubmitted.value},
dataType: "json",
timeout: 2500,
success: function() { alert("???");},
error: function(error1, error2, error3)
{ alert("error"); },
complete: function(arg1, arg2, arg3)
{ alert("complete"); }
});
});
});
</script>
</head>
<body>
<h1>Test form for Ajax</h1>
</br>
Name: <input type="text" id="name" name="name"></br>
<input type="button" value="NameGameIt" id="NameGameIt">
</form>
</body>
</html>
So, when I run the node application, the server comes up just fine. When I fire up the html page, I get the alert "Got here!" When I press the button "NameGameIt", I get the alert "How about now?" followed by the alert "Name: " + whatever name I entered. Once I click off that alert, the node application immediately sees the post and prints out the name to the console. Two seconds later when the node sends the response, the browser will go right into the error handler on the ajax post. The only useful data to come out in the error handler is that it is an "error" which isn't really useful.
So I know the message is getting to the node from the html page because it prints out the name that I sent and I know the html page is getting the message back from the node because it won't error out until the timeout on the node happens triggering the send. But, I have no idea why it keeps on sending me to the error handler instead of the success. I've stepped through all the way in the node code using node-inspector and it seems to be building the packet correctly with a 200 code for success and it calls .end inside .send after it's done making the packet so I don't think either of those things are biting me.
I'm about to go nuts! If anyone sees what I'm missing or has any new ideas on ways to gather more information, I would be very grateful for the help.
Your code is perfectly fine, but you're almost certainly running into a cross-domain AJAX request issue. You might be opening this HTML file on the local filesystem and making requests that way, which is what is causing this problem.
To fix it, add app.use(express.static('public')); like so:
var express = require('/usr/lib/node_modules/express');
var app = express();
app.use(function(err, req, res, next){
console.error(err.stack);
res.send(500, 'Something broke!');
});
app.use(express.bodyParser());
app.use(express.static('public'));
app.post('/namegame', function(req, res)
{
console.log('Got request name: ' + req.body.name);
setTimeout(function()
{
var newName = "New Name-O";
res.send({name: newName});
}, 2000);
}
);
app.listen(8088, function() { console.log("Server is up and running"); });
and then place your html file in the 'public' folder. Once you launch your server, you can visit http://127.0.0.1:8088/file.html and your code will run fine.
In my case adding this to the app.js works.
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
https://enable-cors.org/server_expressjs.html

Categories