i am working on a project on github named lan-info.
i have the following code
let arpSweepCommand = './arpSweep.sh';
app.get('/arp', (req, res) => {
console.log('we have a working signal!');
executeCommand(arpSweepCommand, req, res, false);
fs.readFile('command_output/arpedHosts.txt', 'utf-8', (error, data) => {
if (error) {
res.send(error);
} else {
res.send(data);
}
})
})
and arpSweep.sh contains:
timeout 10s netdiscover -P > command_output/arpedHosts.txt
and in my frontend i have a Jquery AJAX call:
arpButton.click(function () {
loader.show();
$.ajax({
method: 'GET',
url: '/arp',
success: function (data) {
console.log(data);
commandOutput.append(data);
loader.hide();
}
})
})
i know that there are no syntactical errors because webpack compiles the frontend code without complaints; and i know that the backend does catch the request as whenever i click arpButton it prints 'we have a signal!' on the server.
but the problem is loader.show()/hide() seem to do nothing in only this ajax request i know that this is specific to this ajax request because i have similar requests that function perfectly.
the problem for me is the information isn't automatically appended to commandOutput.
i need to reclick arpButton another time for the output to be appended to commandOutput.
but the loader wont show up for me i know that the loader isn't appearing and disappearing very fast because the ajax call takes at least 10 seconds to complete.
the other problem is the data doesn't appear automatically after 10 seconds in commandOutput i tested that only the loader is malfunctioning by waiting for 30 minutes after clicking arpButton but nothing happens; when i proceed to click arpButton again the output is shown instantly.
so why isnt the page updating itself? it instead forces me to reclick the button.
if you need more information you can click the project link above the files are:
src/index.js
index.js
arpSweep.sh
NOTE: be sure to change the subnet in the top of index.js if its not the subnet you use (automatic subnet detection is a feature i plan on implementing later).
EDIT: after trying to read the file as a callback for executeCommand:
function executeCommand (command, req, res, sendRes = true, callback) {
exec(command, (error, stdout, stderr) => {
console.log(`stdout: ${stdout}`)
console.log(`stderr: ${stderr}`)
if (error !== null) {
console.log(`Error ${error}`)
} else if (sendRes === false) {
} else if (typeof callback === 'function') {
callback()
} else {
res.send(stdout + stderr)
}
})
}
app.get('/arp', (req, res) => {
console.log('we have a working signal!')
executeCommand(arpSweepCommand, req, res, false, function () {
fs.readFile('command_output/arpedHosts.txt', 'utf-8', (error, data) => {
if (error !== null) {
res.send(error)
} else {
res.send(data)
}
})
})
})
i am encountering a new problem:
when arpButton is clicked now the loader shows up but after 10 seconds (the time allocated to the command in arpSweep.sh before netdiscover is killed) seems to make no difference and the loader seems to stay there forever now.
and on the server i am catching the following error.
error: Error can't execute command './arpSweep.sh'
the problem for me is the information isn't automatically appended to commandOutput
The issue you have is because the browser doesn't know how to append json (I assume it is not always json response).
Try to append responseText instead of data:
success: function (data, status, xhr) {
console.log(data)
commandOutput.append(xhr.responseText + '\r\n')
}
As of loader - everything OK with it, maybe you just need to hide it not on success, but on complete callback.
In this part:
executeCommand(arpSweepCommand, req, res, false)
fs.readFile('command_output/arpedHosts.txt', 'utf-8', (error, data) => {
You calling executeCommand asynchronously, so you can't be sure that the output is from the current call (that is probably the core of the issue when you need to click twice). So you should move reading file part of the code inside of the exec callback inside of the executeCommand method like this:
function executeCommand (command, req, res, cb) {
exec(command, (error, stdout, stderr) => {
console.log(`stdout: ${stdout}`)
console.log(`stderr: ${stderr}`)
if (error !== null) {
console.log(`Error ${error}`)
} else {
if (typeof cb === 'function') {
cb()
} else {
res.send(stdout + stderr)
}
}
}
}
And
executeCommand(arpSweepCommand, req, res, () => {
fs.readFile('command_output/arpedHosts.txt', 'utf-8', (error, data) => {
if (error) {
res.send(error)
} else {
res.send(data)
}
})
})
The problem is that executeCommand is asynchronous (it uses child_process.exec), so you are reading the file content BEFORE has been written, and that's why you need to click the button again to see the content in the next request. See the example here and how to make it sync-like with promisify and async/await: https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback
Something like this (not tested):
const exec = util.promisify(require('child_process').exec);
async function executeCommand (command, req, res, sendRes = true) {
return exec(command, (error, stdout, stderr) => {
console.log(`stdout: ${stdout}`)
console.log(`stderr: ${stderr}`)
if (error !== null) {
console.log(`Error ${error}`)
} else if (sendRes === false) {
} else {
res.send(stdout + stderr)
}
})
}
and then, in your handler:
await executeCommand(arpSweepCommand, req, res, false);
Related
Im trying to query a MySQL database and see if a record exists in a table
if it does then render page without inserting to a table
if it does not then call MySQL with another query to write to a table and then render page
What I believe is happening is that the first connection.query runs and before it renders the page when the record exists it tries to insert to table and errors with the below, maybe due to trying to render at the same time but not sure? Any help on solving this will be appreciated.
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at ServerResponse.setHeader (_http_outgoing.js:558:11)
exports.follow = async (req, res) => {
try {
pool.getConnection(function (error, connection) {
if (error) {
console.log(error);
return;
}
connection.query(checkExists, async (error, results) => {
if (error)
throw error;
return res.status(200).render('search', {
});
})
connection.query(insertIfDoesNotExist, async (error, results) => {
if (error) throw error;
if (loggedin) {
return res.status(200).render('search', {
});
}
})
}
})
} catch (error) {
console.log(error);
}
}
You're right, connection.query() is asynchronous, so you've end up with race condition. checkExists and insertIfDoesNotExist will be queried synchronously, but it will only run its callback when it gets a reply from the database (this is the async part).
So most probably, you end up calling both call back, and trying to res.render twice, which is not correct. Each HTTP request can only have one response.
So how to solve this? You should nest your callback or use await (if you use a promise version of SQL driver) to something like this
exports.follow = async (req, res) => {
try {
pool.getConnection(function (error, connection) {
if (error) {
console.log(error);
return;
}
connection.query(checkExists, async (error, results) => {
if (error) throw error;
if (!results) // condition to check if it exists here!
// Only insert this after you've confirmed that it does not exists
connection.query(insertIfDoesNotExist, async (error, results) => {
if (error) throw error;
if (loggedin) {
return res.status(200).render('search', {});
}
});
return res.status(200).render('search', {});
});
});
} catch (error) {
console.log(error);
}
};
I am using this async module for asynchronously requesting
web content with the help of another module request, as this is an asynchronous call.
Using async.each method, for requesting data from each link,
the result is also successfully returned by the scrap() function (which I have wrote to scrap returned html data
and return it as array of fuel prices by state).
Now, the problem is that when I try to return prices back to async.each() using cb(null, prices), it shows console.log(prices) as undefined
but logging inside the _check_fuel_prices(), works fine. It seems the callback works with only one argument
(or error only callback, as show as an example in the async.each link above). What if I want to it return prices (I can change it with error like cb(prices), but I also want to log error).
router.get('/someRoute', (req, res, next) => {
const fuels = ['diesel', 'petrol'];
async.each(fuels, _check_fuel_prices, (err, prices) => {
if (!err) {
res.statusCode = 200;
console.log(prices);
return res.json(prices);
}
res.statusCode = 400;
return res.json(err);
});
function _check_fuel_prices(fuel, cb) {
let prices = '';
const url_string = 'http://some.url/';
request(`${url_string}-${fuel}-price/`, (error, response, html) => {
if (error) {
cb(error, null);
return;
}
if (response.statusCode === 404) {
console.log(response.statusCode);
cb('UNABLE TO FIND PAGE', null);
return;
}
prices = scrap(html, fuel);
console.log(prices);
cb(null, prices);
return;
});
}
});
As #generalhenry points out, I was able to get the prices by using async.map which returns error first callback instead of error only apart from that async.series can be used here by slightly changing the code.
I'm writing a rest api for a node application, and I find myself rewriting something like the following a lot:
function(req, res, next) {
databaseCall()
.then( (results) => {
if (results != null) {
res.status(200).send(results);
} else {
res.sendStatus(404);
}
})
.catch(function(err) {
console.log("Request error: " + err.stack);
res.sendStatus(500);
})
}
I would like to refactor the response portion, so I can do something like
databaseCall()
.then(handleResponse)
where handleResponse would take care of the whole response/catch process.
But I can't quite figure out how to do that. The databaseCall method varies depending on the endpoint - sometimes it takes a parameter, sometimes not. I could make a generic function expression that takes the databaseCall result and stick it in the promise chain, but I don't know how I could access the response object inside that function. I know I could add another function to combine everything, like so:
function(databaseCall, parameter, req, res, next) {
databaseCall(parameter)
.then( (results) => {
if (results != null) {
res.status(200).send(results);
} else {
res.sendStatus(404);
}
})
.catch( (err) => {
console.log("Request error: " + err.stack);
res.sendStatus(500);
})
}
But that seems ugly since databaseCall could have 0-several parameters. I'd think there's a more elegant solution.
You're probably thinking in the right direction, you just need to take it a step further and keep the db call outside the generic handler, and pass it as a promise instead
// generic handler for db promise
// the promise is created outside and passed as arg
function responseFromDb(databaseCallPromise, res) {
databaseCallPromise
.then((results) => {
if (results != null) {
res.status(200).send(results);
} else {
res.sendStatus(404);
}
})
.catch((err) => {
console.log(`Request error: ${err.stack}`);
res.sendStatus(500);
});
}
// handler per request, only needs to create db call with the desired params
// and pass it to the generic handler, which will take care of sending the response
function(req, res, next) {
responseFromDb(databaseCall(param1, param2), res)
}
I the following code in my node.js project.
async.eachLimit(dbresult, 1, function (record, callback) {
var json = JSON.stringify(record)
var form = new FormData()
form.append('data', json)
form.submit(cfg.server + '/external/api', function (err, res) {
if (err) {
callback(err)
}
if (res.statusCode === 200) {
connection.query('UPDATE selected_photos set synced = 1 WHERE selected_id = "' + record.selected_id + '"', function (err, result) {
if (err) {
console.log(err)
callback(err)
} else {
callback()
}
})
} else {
console.log(res.statusCode)
return callback(err)
}
})
}, function (err) {
// if any of the file processing produced an error, err would equal that error
if (err) {
// One of the iterations produced an error.
// All processing will now stop.
console.log('A share failed to process. Try rerunning the Offline Sync')
process.exit(0)
} else {
console.log('All files have been processed successfully')
process.exit(0)
}
})
}
res.statusCode = 302 So this should error out. But the the error callback is never triggered. How do it get it to trigger the error so that it stops eachLimit and the shows the
console.log('A share failed to process. Try rerunning the Offline Sync')
You have:
if (err) {
in first line of form submit handler. After that, you are sure that there was no error. So when you check response statusCode and try to call back with error, you are calling back with empty value.
That is why you do not get error when checking for it in your final callback function.
Try to create new Error('Status not OK: ' + res.statusCode) when calling back from your form submit handler.
I am calling function dorequest many times per request to node server.
I have problem with request to webpage running on apache2.2.21. Almost of these request are done without any problems, but several request ending with error ECONNRESET and I don't know why. If I use apapche2.4 then everything going well.
var request = require('request');
function dorequest(set, callback){
request.get(url, function optionalCallback(err, httpResponse, body){
if (err){
console.log(url);
throw err;
} else {
//do some stuffs
}
});
}
Probably your apache server simply drops your request because there are too many connections at the same time initiated by dorequest function.
You can execute those request consequently by calling one in the callback of another by calling the next request in the callback for the previous one, but since there are quite a lot of them and for estetic reasons I would recommend to use async library - it's awesome and really handy when dealing with things like that.
function dorequest(set, callback){
request.get(url, function optionalCallback(err, httpResponse, body){
if (err){
callback(err);
} else {
//do some stuffs
}
callback(err, res);
});
}
var maxRequestAtATime = 30;
async.mapLimit(arrayOfOptions, maxRequestAtATime, dorequest, function(err, results){
// results is now an array of stats for each request
});
If the options of a request depend on the options of the previous one, you should use async.waterfall.
I updated script and use async.queue function for that and still have some err on apache.
function dorequest(set, callback)
{
console.log('add request');
q.push({set: set, callback: callback}, function (err) { });
}
var q = async.queue(function (task, callback) {
setTimeout(function () {
console.log('hello ' + task.set.url, ' lenght: ',q.length());
if (task.set.method=='get')
{
myrequest.get(task.set.url, function optionalCallback(err, httpResponse, body)
{
if (err)
{
console.log(task.set.url);
throw err;
}
else
{
//console.log(set.url,body);
if (typeof task.callback !='undefined') task.callback(body);
callback();
}
});
}
else
{
if (!task.set.data) task.set.data={};
myrequest.post(task.set.url, function optionalCallback(err, httpResponse, body)
{
if (err)
{
console.log(task.set.url);
throw err;
}
else
{
//console.log(set.url,body);
if (typeof task.callback !='undefined') task.callback(body);
callback();
}
}).form(task.set.data);
}
},500);
},1);