nodejs - Weird "Can't set headers after they are sent" case - javascript

I'm having a strange Can't set headers after they are sent crash on my NodeJs/Express app.
The crashing request : POST /auth/form
How the request is server-side handled
app.js
[...]
var auth_route = require('./routes/auth');
app.use('/auth', auth_route );
[...]
auth.js
var AuthController = require('../controller/auth_controller');
[...]
router.post("/form", function(req, res) {
[...]
auth_ctrl.form_login(username, password);
});
auth_controller.js
AuthController.prototype.form_login = function(username, password) {
_this.user_model.get_by_username(username, function(user) {
if (user == null)
return (Response.send_204(_this.res, "User not found"))
password.isTheSame(password, user.password, function(err, res) {
if (err)
return (Response.send_error_response(_this.res, 500, "Internal server error occured, sorry about that.."));
if (!res)
return (Response.send_error_response(_this.res, 401, "Wrong password"));
// Crash seems to happen on the above 401 response which is the 67th lines of my auth_controller file (cf. callstack bellow)
_this.start_user_session(user, function(err) {
if (err)
return (Response.send_error_response(_this.res, 500, "Internal server error"));
return (Response.send_200(_this.res, "You are logged!"));
})
});
})
}
Response.send_error_response source code if needed
function send_error_response(res, code, message, missing_fields) {
[..]
res.header('Content-Type', 'application/json')
res.status(code).send(JSON.stringify({
[..]
}));
}
The callstack trace
POST /auth/form 401 2.408 ms - -
_http_outgoing.js:356
throw new Error('Can\'t set headers after they are sent.');
^
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:356:11)
at ServerResponse.header (C:\Users\ME\dev\app\node_modules\express\lib\response.js:719:10)
at Object.send_error_response (C:\Users\ME\dev\app\core\lib\Response.Library.js:30:6)
at C:\Users\ME\dev\app\controller\auth_controller.js:67:22
How I make this crash happen
I keep pushing down my enter button which send a looot of requests from the same origin.
It seems that's happening inside password callback function...
Any suggestions ? Thanks in advance !

My guess is that your implementation of AuthController does something like this:
var _this;
function AuthController(req, res) {
this.req = req;
this.res = res;
_this = this;
}
This "promotes" _this to a (module-wide) global, which gets overwritten for every request that posts to /auth/form. If two of those requests are sent in quick succession, you could end up with the situation that a response is send using the same _this more than once, which would result in the error that you got:
request 1 comes in, _this points to its controller instance
request 2 comes in, _this gets overwritten to point to its controller instance
request 1 is done and sends back a response, using the _this that belongs to request 2
request 2 is done and sends back a response, using the same _this, resulting in an error
So, don't use globals, use this instead:
AuthController.prototype.form_login = function(username, password) {
this.user_model.get_by_username(username, function(user) {
...
});
};
You can always create a function-scoped variable to hold a reference to it, if preferable:
AuthController.prototype.form_login = function(username, password) {
var _this = this;
_this.user_model.get_by_username(username, function(user) {
...
});
};

Related

Node express references error response is not defined

var _expressPackage = require("express");
var _bodyParserPackage = require("body-parser");
var _sqlPackage = require("mssql");
//Initilize app with express web framework
var app = _expressPackage();
//To parse result in json format
app.use(_bodyParserPackage.json());
***//Here we will enable CORS, so that we can access api on cross domain.***
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, contentType,Content-
Type,
Accept, Authorization");
next();
});
***//Lets set up our local server now.***
var server = app.listen(process.env.PORT || 4000, function () {
var port = server.address().port;
console.log("App now running on port", port);
});
***//Set up your sql connection string, i am using here my own, you have to replace it with your
own.***
var dbConfig = {
user: "sa1",
password: "12345",
server: "localhost",
database: "test123"
};
***//Function to connect to database and execute query***
var QueryToExecuteInDatabase = function (response, strQuery) {
***//close sql connection before creating an connection otherwise you will get an error if
connection already exists.***
_sqlPackage.close();
//Now connect your sql connection
_sqlPackage.connect(dbConfig, function (error) {
if (error) {
console.log("Error while connecting to database :- " + error);
response.send(error);
}
else {
***//let's create a request for sql object***
var request = new _sqlPackage.Request();
//Query to run in our database
request.query(strQuery, function (error, responseResult) {
if (error) {
console.log("Error while connecting to database:- " + error);
response.send(error);
}
else {
response.send(responseResult);
}
});
}
});
}
***//GET API***
app.get("/StudentList", function(_req ,_res){
var Sqlquery = "select * from student1"; ***//tbl_studentdetails***
QueryToExecuteInDatabase(_res, Sqlquery);
});
***//call a stored procedure***
var request = new _sqlPackage.Request();
***//calling a stored procedure***
request.input('Username', _sqlPackage.VarChar(50), 'admin');
request.input('Password', _sqlPackage.VarChar(50), 'admin#123');
request.execute('sp_CheckLogin', function (err, recordsets, returnValue) {
response.send(recordsets);
});
> (D:\performalytic\9999.practice\angularpra\NodeApiWithSql\node_modules\mssql\lib\tedious\request.js:701:23)
at processImmediate (internal/timers.js:463:21)
- end snippet -->
This question could use a bit more clarity... but with the limited information provided it appears the issue you are running into here has to do with lexical scope.
Lexical scope most simply has to do with what variables the current execution context has access to. Inside of a function, you can either access variables declared within the function... or in the surrounding code. The last line of your code snipped shows a top level variable request and a method on that object called execute.
The callback you are passing the execute method has three variables (function parameters) you're naming err, recordsets, and returnValue. Yet inside that function body you're attempting to access a variable named response. If we look in the surrounding code... there is no response variable declared. (The only variable named response I see is within the QueryToExecuteInDatabase, and therefore only accessible within that function body.
Where are you getting this templated code from?

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.

Node.JS returns a 404 on interrogation of an endpoint that actually exists

I have a web application, started by a previous company, written in Angular.JS. The application exposes a request towards the back-end (written in Node.JS+Express) to gather some data required to fill a table. Specifically, this is the request that the application sends everytime the user enters in the page that holds the table (The config variable holds the access token).
return $http.get(API + '/api/myPath/for/Having/Data', config).then(handleSuccess, handleError);
handleSuccess and handleError are so defined
handleSuccess: function (res) {
debugger;
var deferred = $q.defer();
res.data.success ? deferred.resolve(res.data) : deferred.reject(res.data.message);
return deferred.promise;
},
handleError: function (error) {
return {
success: false,
message: error
};
}
In my back-end I've put an a listener to whatever gets called with the "/api" prefix, like this
app.use('/api', authorization.validateToken);
And another listener, that should work only if there is no match (written at the very end of the file that handles all the general inquiries of the app)
app.all('*', (req, res) => {
console.log('Hi, Stack Overflow!');
res.send({
success: false,
status: 404,
message: 'Invalid Uri Resource'
});
});
And, lastly, this is the endpoint that should get called in the back-end from Angular.js
app.get('/api/myPath/for/Having/Data', something.somethingToCall);
Here's the funny part: for a reason that I still have to understand, Angular.JS calls that endpoint twice, resulting in one failing procedure (404) and another one that goes smoothly (200).
The operation flow should be like this: Angular calls the back-end --> Node checks the validity of the token --> executes operation if everything goes okay.
The operation is called twice (seen thanks to the Visual Studio Code debugger and Chrome's Network Monitor) and, even though the token's validation process is correctly executed everytime, the first time the next() function will hold the app.all() listener.
Also,even before I start debugging the first request that is sent out, the JavaScript console on Google Chrome warns me that there has been an error such as like "Cannot read property 'data' of undefined", meaning that the request gets executed twice with the first time returning a 404.
exports.validateToken = (req, res, next) => {
console.log(`check user here`);
// next();
var token = //I take the token
console.log(token);
if (token) {
jwt.verify(token, require('../../secret'), (err, decoded) => {
if (err) {
res.send({
success: false,
status: 500,
tokenExpired: true,
message: "Effettua nuovamente l'accesso"
});
} else {
req.decoded = decoded;
next();
}
});
} else {
res.send({
success: false,
status: 406, // Fprbidden
message: 'User not Authenticated'
});
}
};
Does anybody know how to help me somehow?
EDIT: this is an example of how Chrome's sees both requests. The screenshot, in particular, refers to the first one that gets called and produces the 404
The CORS is handled in the back-end like this
app.use(function (req, res, next) {
if (req.headers.origin && (req.headers.origin.match("http:\/\/somewebsite.com.*") || req.headers.origin.match("http:\/\/localhost:8010") )) {
res.header("Access-Control-Allow-Origin", req.headers.origin);
}
next();
});
Also, I'm adding the endpoint that needs to be called. This also exploits MongoDB + Mongoose for querying the DataBase and return stuff to the front-end. The parameters that I'm passing are pageSize (how many elements per page) and the current page number
exports.getAds = (req, res) => {
var criteria = req.body || {};
var pageSize = criteria['pageSize'] ? Number(criteria['pageSize']) : undefined;
var pageNumber = criteria['pageNumber'] ? Number(criteria['pageNumber']) : undefined;
var sort = criteria.sort || { createdAt: 'desc' };
if (criteria.customerName) criteria.customerName = { $regex: `.*${criteria.customerName}.*`, $options: 'i' };
if (criteria.spentEuros) criteria.spentEuros.$gte = criteria.spentEuros;
if (criteria.referralMail) criteria.referralMail = { $regex: `.*${criteria.referralMail}.*`, $options: 'i' };
console.log(criteria);
var columns = "customerName duration spentEuros";
if (pageSize && pageNumber) {
Adv.paginate(criteria, {
page: pageNumber,
limit: pageSize,
select: columns,
sort: sort
}, function (err, result) {
if (!err) res.status(200).send({ success: true, data: result });
else res.status(500).send({ success: false, message: err });
});
} else {
Adv.find(criteria)
.select(columns)
.sort(sort)
.exec(function (err, result) {
if (!err) res.status(200).send({ success: true, data: result });
else res.status(500).send({ success: false, message: err });
});
}
};
EDIT2: Solution to the question: adding an app.options listener in the back-end (as pointed out by #slebetman), alongside with the already existing app.get one, solved the issue
Here's the funny part: for a reason that I still have to understand, Angular.JS calls that endpoint twice...
That sounds a lot like the browser sending a CORS preflight OPTIONS request, followed by a GET. Check the HTTP verb being used, and be sure you're handling OPTIONS (not just GET) if you need to support CORS on your endpoint. (If you're not expecting this to be a cross-origin request, check the origin of the page relative to the origin of the API call, something [protocol, port, domain] seems to be different — if it's an OPTIONS call.)

Javascript: trouble getting return data from api call

I am a little stuck trying to get a token from the server code to the client. In my server code my call works and a token is returned as var clientToken. The problem is that when I return the response I am getting undefined on my client side.
I am thinking it is because the return token is happening before the response is returned but I am having trouble fixing it.
server code:
'client.token.braintree'(submission){
var token = gateway.clientToken.generate({}, function (err, response) {
var clientToken = response.clientToken
console.log("token", clientToken);
return clientToken
});
return token
},
client code:
Meteor.call('client.token.braintree', {customer_id:
this.props.customer_id}, (err,res) => {
if(!err) {
console.log("tokens client side", res)
}
});

MYSQL + Node.JS Post Request Confusion

I am very new to networking and I have this code which, when I use a REST API like Postman, does exactly what I want it to do:
router.post('/', function(req,res,next){
var reqObj = req.body;
console.log(reqObj);
req.getConnection(function(err, conn){
if(err)
{
console.error('SQL Connection error: ', err);
return next(err);
}
else
{
var query = conn.query("INSERT INTO coordinates (id,lat,lon) VALUES(3,2,1);");
if(err)
{
console.error('SQL error: ', err);
return next(err);
}
res.json("Coordinates sent.");
}
})
} );
That is, it sends the query request to the MYSQL database. My question is, how do I do this without using Postman to send the POST request?
Thank you.
You can't unless you make a post request from within your application or something. If you don't intend on sending data, you can just make it a GET request by changing
router.post('/', function(req,res,next){
to
router.get('/', function(req,res,next){
Then you can just go to the relevant URL from your browser. If you're using chrome and you just wanna see the JSON data, I'd also recommend installing the JSONView chrome extension.
EDIT
Here's the example request using request-promise
var request = require('request-promise');
var objectData = {
name: 'Bruce',
alias: 'Batman'
};
var options = {
method: 'POST',
uri: 'http://your.api/endpoint/',
body: objectData,
json: true // Automatically stringifies the body to JSON
};
request(options).then(function(response){
// handle success response
}, function(error){
// handle error response
})

Categories