Node/Express route not waiting for Redis server callback - javascript

What specific changes need to be made to the code below so that the res.json(...) command is NOT sent until any open calls to the Redis client.somecommand(..) have returned?
The code below is throwing an error related to the client.hmset(uname, { ... } trying to set a response header after res.json(...) was called. When I move the return res.json() commands to right after the end of the client.exists(uname, function(err, reply) { ... } conditional block, instead of their current locations inside the block, the anonymous token value is sent to the client app instead of the generated token value. This indicates that the callbacks to the Redis server have not returned.
How do I change the code below so that the res.json( ... ) commands cannot run until the Redis server callback has been returned? Ideally, there would be some conditional to wait a certain period before sending an error message if the Redis server callback takes too long.
Redis is added to the routes.js file containing all the code below, by adding the following two lines to the top of the file:
var redis = require('redis');
var client = redis.createClient();
Here are the various calls to the Redis server in the code below:
client.exists(uname, function(err, reply) { ... }
client.hgetall(uname, function(err, object) { ... }
client.hmset(uname, { ... }
client.expire(uname, 10);
The complete code for the Node.js/Express.js API route is:
app.get('/user**', function(req, res) {
console.log("You Hit The User Route TOP");
request({
method: 'GET',
url: authServer + '/uaa/user',
json: true,
auth: {
user: null,
password: null,
sendImmediately: true,
bearer: bearerToken
}
}, function (error, response, body) {
if(error){
console.log('ERROR with user request.');
return res.sendStatus(500);
}
else {
var uname = '';var jwtUser = 'empty';var jwtJSON = { "token" : "anonymous" }
console.log(response.statusCode);
if(body['name']){
uname = body['name'];console.log('uname is: ');console.log(uname);
if(uname.length > 0) {
scopesLocal = body['oauth2Request'].scope.toString();
client.exists(uname, function(err, reply) {//Check to see if a Redis key for the user already exists
if (reply === 1) {//a redis key DOES exist
console.log('\"'+uname+'\" exists');
client.hgetall(uname, function(err, object) {//retrieve all the values in the hash/object that we just set
if(object) {
if(object["jwt"]) {
console.log('object[\"jwt\"] is: ');console.log(object["jwt"]);
jwtJSON = { "token" : object["jwt"] };
console.log('jwtJSON is: ');console.log(jwtJSON);
return res.json(jwtJSON);
}
}
});
} else {//a redis key DOES NOT exist
console.log('\"'+uname+'\" doesn\'t exist');
jwtUser = generateJwt(uname, authoritiesLocal);
client.hmset(uname, {//store a hash/object
'AccessToken': body['details'].tokenValue,
'TokenType': body['details'].tokenType,
'Authenticated': body['authenticated'],
'Principal': body['principal'],
'Scopes': scopesLocal.toString(),
'Authorities' : authoritiesLocal,
'jwt' : jwtUser
});
jwtJSON = { "token" : jwtUser };console.log('jwtJSON is: ');console.log(jwtJSON);
return res.json(jwtJSON);
}
client.expire(uname, 10);//set the key to expire in 10 seconds. use this to manage session length
});//end of Redis conditional block
console.log('jwtJSON is: ');console.log(jwtJSON);
} else { console.log('uname is empty!'); }
return res.json(jwtJSON);
}
};
});
console.log("You Hit The User Route BOTTOM");
});
The error message in the nodemon terminal is:
_http_outgoing.js:346
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:346:11)
at ServerResponse.header (/home/user/nodejs_apps/oauth_seed_app/node_modules/express/lib/response.js:719:10)
at ServerResponse.json (/home/user/nodejs_apps/oauth_seed_app/node_modules/express/lib/response.js:247:10)
at Command.callback (/home/user/nodejs_apps/oauth_seed_app/app/routes.js:112:44)
at normal_reply (/home/user/nodejs_apps/oauth_seed_app/node_modules/redis/index.js:714:21)
at RedisClient.return_reply (/home/user/nodejs_apps/oauth_seed_app/node_modules/redis/index.js:816:9)
at JavascriptRedisParser.Parser.returnReply (/home/user/nodejs_apps/oauth_seed_app/node_modules/redis/index.js:188:18)
at JavascriptRedisParser.execute (/home/user/nodejs_apps/oauth_seed_app/node_modules/redis-parser/lib/parser.js:413:12)
at Socket.<anonymous> (/home/user/nodejs_apps/oauth_seed_app/node_modules/redis/index.js:267:27)
at emitOne (events.js:90:13)
at Socket.emit (events.js:182:7)
at readableAddChunk (_stream_readable.js:153:18)
at Socket.Readable.push (_stream_readable.js:111:10)
at TCP.onread (net.js:534:20)
I read this posting about the specific error message. And I read this other posting about how to wait for a callback. I also read this posting about Redis callbacks to Node. But I do not see how to apply the answers to either of those other postings to the Redis callback problem in the code above.

The problem was that the return res.json(jwtJSON); commands in the OP were NOT segregated into discrete if...else blocks.
The solution is:
if(something) {
//populate jwtJSON with a real JWT
return res.json(jwtJSON);
} else {
return res.json(jwtJSON);//this leaves the anonymous value
}
Doing this segregation for every nested condition in the above code got rid of the problem.

Related

Error: [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client, fetch error

This code doesn't allow me to make a fetch request when I invoque sendPushMessages(message) due to HTTP ERRORS but I have no idea why.
The console shows:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Moreover, if I comment: res.status(200).send({shop: shop[0]}); the code isn't still working.
const {models} = require('../models');
const Sequelize = require('sequelize');
exports.findshop = async (req, res, next) => {
sendPushMessages = async (message) => {
try{
let response = await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-encoding': 'gzip, deflate',
'Content-Type': 'application/json',
},
body: JSON.stringify(message),
});
let responsed = await response.json();
console.log(responsed);
}
catch (error) {
next(error);
}
}
try {
console.log(req.body.latitud)
console.log(req.body.longitud)
const user = req.user;
const shops = await models.Shop.findAll({
include:[{ model: models.Product, as: 'placements'}]
});
const shop = shops.filter(shp => {
if (Math.abs(shp.latitud - req.body.latitud) <= 0.001 && Math.abs(shp.longitud - req.body.longitud) <= 0.001){
return shp;
}
});
if (shop[0] && user) {
shop[0].placements.forEach(item => {
if (item.dataValues.isoffer){
const message = {
to: user.pushtoken,
sound: 'default',
title: '¡Ofertón! ¡No te lo puedes perder!',
body: item.productname + ' ' + item.price.toString() + '€',
};
sendPushMessages(message);
}
})
res.status(200).send({shop: shop[0]});
} else {
res.status(200).send({shop: null});
}
} catch (error) {
next(error);
}
};
From the code you show (it would be useful to see the top level request handler too so we can see the whole flow of the code), there is an issue if sendPushMessages() has an error. It will call next(err) and keep going in your outer loop which will try to send at least one more response, causing the error message you get for ERR_HTTP_HEADERS_SENT.
You need some error handling upon calling sendPushMessages() so you can stop your loop when it has an error. So, that needs to be fixed. One simple way to fix that would be to remove the catch() block from sendPushMessages() and just let that rejection propagate back to the caller. Then, add an await in front of your call to sendPushMessages(message) like this:
await sendPushMessages(message);
So, when sendPushMessages() rejects, you will hit your own catch block in findshop() and will call next(error) just once and not send any other response.
If the rest of your question is why are you getting an error when you try to access https://exp.host/--/api/v2/push/send', then we can only probably help if you can share what the error is your actually getting. You should be able to add console.log(error) to your catch() handler so you can log the actual error you are getting. In my server code, I always log the actual lowest level error that is occurring because that is often needed to debug why a problem is happening.
This the error that appears:
fetch is not defined
POST /location 500 8.371 ms - 32
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:526:11)
at ServerResponse.header (/home/aitorencinas/Documents/Universidad/TFG/servidor/server_express/node_modules/express/lib/response.js:767:10)
at ServerResponse.send (/home/aitorencinas/Documents/Universidad/TFG/servidor/server_express/node_modules/express/lib/response.js:170:12)
at done (/home/aitorencinas/Documents/Universidad/TFG/servidor/server_express/node_modules/express/lib/response.js:1004:10)
at Object.exports.renderFile (/home/aitorencinas/Documents/Universidad/TFG/servidor/server_express/node_modules/jade/lib/index.js:374:12)
at View.exports.__express [as engine] (/home/aitorencinas/Documents/Universidad/TFG/servidor/server_express/node_modules/jade/lib/index.js:417:11)
at View.render (/home/aitorencinas/Documents/Universidad/TFG/servidor/server_express/node_modules/express/lib/view.js:135:8)
at tryRender (/home/aitorencinas/Documents/Universidad/TFG/servidor/server_express/node_modules/express/lib/application.js:640:10)
at Function.render (/home/aitorencinas/Documents/Universidad/TFG/servidor/server_express/node_modules/express/lib/application.js:592:3)
at ServerResponse.render (/home/aitorencinas/Documents/Universidad/TFG/servidor/server_express/node_modules/express/lib/response.js:1008:7)

Why socket.io listener in android side not called?

I write socket middleware for user Authentication but I don't know what's my problem!
in android side "unauthorized" event not called.
Server-side :
async function (socket, next) {
if (socket.handshake.query && socket.handshake.query.token) {
jwt.verify(socket.handshake.query.token, process.env.SECRET_JWT_KEY, function (err, decoded) {
if (err) {
socket.emit( "unauthorized", "unauthorized user") <==send to sender you'r authentication is failed
console.log("Query: ", "unauthorized"); <==This line is lunched
return next(new Error('Authentication error'));
}
socket.decoded = decoded;
next();
});
} else {
next(new Error('Authentication error'));
}
Client-side :
val socket = IO.socket("http://192.168.1.6:3000", IO.Options().apply {
path = "/sockets/chat"
query = "token=wrongtoken"
})
socket.on("unauthorized", object : Emitter.Listener { <== THIS NOT LUNCED!!
override fun call(vararg args: Any?) {
println("user authentication failed" )
}
}
I think you need to connect to the socket first by calling this line inside apply block
this.connect()
Finally found the solution no need to emit from middleware when throwing an error you can get predefined events in an android client like this:
socket.on(Socket.EVENT_ERROR) { args -> println(args.contentToString()) }

How to solve nodejs uncaughtException: Connection already released error and MaxListenersExceededWarning?

I am building an express server to receive request (a dict with 10 items) from my react front end and then save the data to database. Below is my code. I found that my code is work and the query does save the record back to Db. But in each for loop, this error is returned in server. What cause this error and the MaxListenersExceededWarning?
The request data:
{{.....}, {.....}, {.....}, {.....}, {.....}} #10 item
Code:
connection.js:
const p = mysql.createPool({
"connectionLimit" : 100,
"host": "example.org",
"user": "test",
"password": "test",
"database": "test",
"multipleStatements": true
});
const getConnection = function(callback) {
p.getConnection(function(err, connection) {
callback(err, connection)
})
};
module.exports = getConnection
routers.js
router.post('/test', (req, res) => {
getConnection(function(err, conn){
if (err) {
return res.json({ success: false, error: err })
} else {
const dict = req.body;
Object.keys(dict).forEach(function(r){
#putting dict's value to query
query = "UPDATE ......;"
conn.query(query, function (err, result, fields) {
conn.release()
console.log(query)
if (err) {
console.log("err")
return res.json({ success: false, error: err });
}
});
});
}
});
return res.json({ success: true });
});
Error:
error: uncaughtException: Connection already released
Error: Connection already released
at Pool.releaseConnection (/home/node_modules/mysql/lib/Pool.js:138:13)
at PoolConnection.release (/home/node_modules/mysql/lib/PoolConnection.js:35:15)
at Query.<anonymous> (/home/routes/test.js:276:22)
at Query.<anonymous> (/home/node_modules/mysql/lib/Connection.js:526:10)
at Query._callback (/home/node_modules/mysql/lib/Connection.js:488:16)
at Query.Sequence.end (/home/node_modules/mysql/lib/protocol/sequences/Sequence.js:83:24)
at Query._handleFinalResultPacket (/home//node_modules/mysql/lib/protocol/sequences/Query.js:149:8)
at Query.OkPacket (/home//node_modules/mysql/lib/protocol/sequences/Query.js:74:10)
at Protocol._parsePacket (/home//node_modules/mysql/lib/protocol/Protocol.js:291:23)
at Parser._parsePacket (/home//node_modules/mysql/lib/protocol/Parser.js:433:10)
(node:15881) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 finish listeners added. Use emitter.setMaxListeners() to increase limit
One connection (conn) is being retrieved from the pool, and is used to launch 10 queries in the forEach loop.
When the first query finishes to run, the first step of its callback is: conn.release(). The connection is released.
When the second query finishes to run, its callback also tries to release the connection,causing the error.
This problem might be solved in multiple ways:
Solve using a counter
In the callback of the database query, before calling call.release, check the number of queries already processed, and only close the connection when the last product is being processed.
const dict = req.body;
// initialize counter
let itemCount = 0
, errors = []
Object.keys(dict).forEach(function(r){
#putting dict's value to query
query = "UPDATE ......;"
conn.query(query, function (err, result, fields) {
// check whether this is the last callback
if (itemCount === dict.length-1) {
conn.release()
let result = errors.length ? { success: false, error: errors } : { success: true }
res.json(result)
}
// increment counter
itemCount++
console.log(query)
if (err) {
console.log("err")
errors.push(err)
}
});
});
Edit: There is also an issue with the res.json calls: inside the code in the question, res.json({ success: true }) is always executed, without waiting for the queries' execution results. The modified code sample above calls res.json only once after the execution of all queries, this is the only place where res.json should be called. This implies modifying the client-side code so that it can handle an array of errors, rather than only one error.
Solve by using a recursive function instead of for loop.
It is not a good practice to use for loops for the execution of asynchronous code. You might run into Maximum call stack size exceeded errors whenever the data volume gets too large.
Instead, create a recursive function (e.g. updateDictItem) to process one update query at a time. Read more about the asynchronous patterns in node.js in this article.
Other possible enhancements
Rather than firing ten database queries, it is worth considering grouping all the updates in one MERGE update statement, otherwise doing all the updates in a TRANSACTION.

Passing parameter from app get function of one file to another

How can I give a function that gets called in one file a parameter to use in another file. I am trying to create a RestApi with node.js and express.js. The file entry.routes.js contains the following code:
app.get("/energy/api/ActualTotalLoad/:AreaName/:Resolution/date/:Year-:Month-:Day", entry.findTwo);
However in this link there are some parameters inside the header as a token. I decode the token with the following code:
app.use(
jwt({
secret: privateKey,
credentialsRequired: false,
getToken: function fromHeaderOrQuerystring (req) {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
return req.headers.authorization.split(' ')[1];
} else if (req.query && req.query.token) {
return req.query.token;
}
return null;
}
}));
In the file entry.controllers.js the code is the following:
exports.findTwo =async function (req, res) {
console.log(req);
const correct =await getResults2(req.user.apikey);
console.log(correct);
if (correct==1){
Entry.findByPars(req.params.AreaName,req.params.Resolution,req.params.Year,req.params.Month,req.params.Day, (err, data) => {
if (err) {
if (err.kind === "not_found") {
res.status(403).send({
message: `No data`
});
} else {
res.status(400).send({
message: "Bad Request"
});
}
} else{
if(req.query.format!==undefined && req.query.format=="csv"){
const {Parser} = require('json2csv');
const json2csvParser=new Parser();
const csv=json2csvParser.parse(data);
res.send(csv);
}
else if (req.query.format==undefined || req.query.format=="json"){
res.send(data);
}
else {
res.status(400).send({
message: "Bad Request"
});
}
}
});
}
else if (correct==2) res.status(402).send({ message: "Out of Quota"});
else res.status(401).send({message: "Not authorized"});
}
In the last code when I do the following command
const correct =await getResults2(req.user.apikey);
where I try to access req.user.apikey . This parameter is available in other app.post commands in the entry.routes file but is not accessible in the other file. It passes as undefined.
For example in the entry.routes file the following code works perfectly:
app.post("/energy/api/Logout",function(req,res){
console.log(req.user.apikey);
var jwt=require('jsonwebtoken');
sql.query(`SELECT apikey FROM users WHERE apikey=?`,[req.user.apikey],(err,res1) => {
if (err) {
console.log("error: ", err);
result(err, null);
return;
}
else if (res1.length){
res.status(200).send(" ");
}
else res.status(400).send("Bad Request");
});
});
Why doesn't the parameter req.user.apikey get passed in the entry.findTwo function on the file entry.controllers?
💡 The One reason why you can only get the value from req.user.apikey inentry.controller when you send request to this endpoint:
app.get("/energy/api/ActualTotalLoad/:AreaName/:Resolution/date/:Year-:Month-:Day", entry.findTwo);
It's because you add authorization to the header, before you send the request.
👨‍🏫 It should be noted: this req.user.apikey is comming from the access token that you provided in the header before sending the request.
Because you're using express-jwt, so, every token that you include in the header will be decoded directly and added to the req parameter, that's why you can access req.user.apikey.
So, if your question ❓ is why req.user.apikey can only be accessed inentry.controller, then the answer is because on your other route, for example:
When you call this route: energy/fire/logout
You didn't add authorization there.
💡 So, To be able to use req.user.apikey on your other route, make sure in your header you have added authoriazation.
I hope it can help you 🙏.

Angular2: PUT request to Node-Server doesn't work - authentication lost

I'm using Angular2 on the client side and a node-express server as my backend. The node-server works as an API-middleware and also as my authentication service. The user-requests must contain a valid JWT token to perform requests on the node-server.
All of my GET functions and other PUT functions are working properly. I wrote a new one, which just should delete an ID on a third-party API, doesn't.
Furthermore, my node-express server sends custom error messages at some points to the client. This comes to my problem, whenever I run my latest PUT-function, my server responds with "No token provided". This happens when the user isn't logged in on the client side.
As I said, all my other functions working. this.createAuthenticationHeaders(); is necessary to perform valid request on the server side. But it's implemented.
In other words, the authentication gets lost between client and server and I get my own error message: "No token provided".
Appointment-Detail.Component.ts
cancelAppointment() {
this.authService.getProfile().subscribe(profile => {
this.username = profile.user.username; // Set username
this.email = profile.user.email; // Set e-mail
if (profile.user.email) {
this.apiService.cancelUserAppointment(this.id).subscribe(data => {
console.log(this.id);
if (!data.success) {
this.messageClass = 'alert alert-danger'; // Set error bootstrap class
this.message = data.message; // Set error message
} else {
this.messageClass = 'alert alert-success'; // Set success bootstrap class
this.message = data.message; // Set success message
// After two seconds, navigate back to blog page
}
});
}
});
}
API Service
cancelUserAppointment(id) {
this.createAuthenticationHeaders();
console.log('API SERVICE ' + id);
return this.http
.put(this.domain + 'api/appointments/' + id + '/cancel', this.options)
.map(res => res.json());
}
An API Service functions that works
getCertificatesByUser(email) {
this.createAuthenticationHeaders();
return this.http
.get(this.domain + 'api/user/' + email + '/certificates', this.options)
.map(res => res.json());
}
Server route to the third party API
router.put('/appointments/:id/cancel', (req, res) => {
console.log('hi');
var id = req.params.id;
const url = process.env.acuityUri + '/appointments/' + id + '/cancel';
console.log(id);
});
Authentication middleware
router.use((req, res, next) => {
const token = req.headers['authorization']; // Create token found in headers
// Check if token was found in headers
if (!token) {
res.json({
success: false,
message: 'No token provided'
}); // Return error
} else {
// Verify the token is valid
jwt.verify(token, config.secret, (err, decoded) => {
// Check if error is expired or invalid
if (err) {
res.json({
success: false,
message: 'Token invalid: ' + err
}); // Return error for token validation
} else {
req.decoded = decoded; // Create global variable to use in any request beyond
next(); // Exit middleware
}
});
}
});
Without doing too much of a deep dive into your auth headers, I see a pretty glaring issue that I think may be the cause of your troubles.
HTTP REST verbs carry different "intents", the intent we specifically care about in this case is wether or not your request should have a body.
GET requests do not carry a body with them.
PUT requests do carry a body.
Because of this, angular's HttpClient request methods (http.get, http.post, etc.) have different method signatures.
To cut to the chase, http.put's method signature accepts 3 parameters: url, body, and options, whereas http.get's method signature only accepts 2: url and options.
If you look at your example, for http.put you are providing this.httpOptions as the second parameter instead of the third, so Angular is packaging up your options object as the PUT request body. This is why you have a working example and a non-working example; the working example is a GET!
The solution? Simply put something else as the request body in the second parameter and shift this.options down to the third parameter slot. If you don't care what it is, just use the empty object: {}.
So your request should look like this:
return this.http
.put(this.domain + 'api/appointments/' + id + '/cancel', {}, this.options)
At the very least, this should send whatever is in this.options to the server correctly. Now wether what's in this.options is correct or not is another story.
Example PUT call from Angular's docs: https://angular.io/guide/http#making-a-put-request

Categories

Resources