Another "can't set headers after they're sent" - node.js

I get a "can't set headers after they're sent" error, which I understand may be due to sending a response from my server more than once, however it doesn't seem to be the case with my code, at least on the surface. I make a query to dbpedia in order to obtain some resources, which I then send back to my client. Here's the code:
app.get("/",function(req,res,next){
if (req.query.titolo){
var response ;
let [ artist, title ] = getArtistTitle(req.query.titolo);
title = title.replace(" ","_");//for dbpedia resource names
const prefixes = {
dbo: "http://dbpedia.org/ontology/",
db: "http://dbpedia.org/resource/"
}
DbPediaClient.setOptions('application/sparql-results+json',prefixes);
DbPediaClient.query("SELECT ?abstract WHERE { db:"+title+
" dbo:abstract ?abstract. FILTER langMatches(lang(?abstract),'en') }")
.then((data)=>{
response = data["results"]["bindings"][0]["abstract"]["value"] ;
return res.json({abstract: response}) ;
})
.catch((error)=>{
console.log(error);
});
}
});
I use virtuoso-sparql-client to make a query to http://dbpedia.org/sparql (DbPediaClient is initialized outside of this function). I've already tried using res.send instead of res.json, and it still gives the same error. Which by the way it's the following:
Error: Can't set headers after they are sent.
at validateHeader (_http_outgoing.js:491:11)
at ServerResponse.setHeader (_http_outgoing.js:498:3)
at ServerResponse.header (/home/lorenzo/Scrivania/ProgettoTechweb/AlphaTube/node_modules/express/lib/response.js:767:10)
at ServerResponse.send (/home/lorenzo/Scrivania/ProgettoTechweb/AlphaTube/node_modules/express/lib/response.js:170:12)
at DbPediaClient.query.then (/home/lorenzo/Scrivania/ProgettoTechweb/AlphaTube/server.js:43:15)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
If you need more info please ask.

Express is a framework that handles request/response cycles by iterating through routes that match the path specified in an HTTP request and letting your code send the response in each match or call next() if it didn't send one. The problem here is you have the following:
app.get("/",function(req,res,next){
res.sendFile(...);
next();
});
app.get("/",function(req,res,next){
if (req.query.titolo){
...
res.json(...);
}
});
This code means
Whenever I get an HTTP request to / then send some HTML, then if the request contains titolo in the query, send some JSON as well.
Express is telling you that you can't send anything else since you already sent the HTML.
One solution would be to move the API route above the HTML:
app.get("/",function(req,res,next){
if (req.query.titolo){
...
res.json(...);
} else {
next();
}
});
app.get("/",function(req,res){
res.sendFile(...);
});
which means
Whenever I get an HTTP request to /, if the request contains titolo in the query, send some JSON, otherwise send some HTML.
But my recommendation would be to move the API route to a different path that doesn't collide with the route for your static file, which is a lot more typical for HTTP servers.

Related

Set response header along with a string

I am trying to send the token in the headers of an HTTP request from backend to the frontend along with sending my own defined string. However, I am getting an issue. The token is being printed as null on the client-side. I don't know why:
Here's my code:
Node/Express
if (bcrypt.compareSync(passcode, results[0].password))
{
const token = jwt.sign({id: email}, secret, {expiresIn: 86400 });
console.log(token);
if(results[0].userrights == 'full')
{
res.setHeader('x-access-token', token);
res.send("Full Authorization");
}
//rest of the code
}
Angular
this.http.post('http://localhost:3000/api/login', form.value, {responseType: "text", observe:
'response'})
.subscribe(responseData => {
console.log(responseData);
console.log(responseData.headers.get('x-access-token')); //prints null on the console
I have searched quite a bit and found different examples which is making it very confusing. I don't want to use response status rather my own defined string. I have tried different things to print the variable but it still is throwing as null.
If you are using a browser extension to allow CORS requests then Access-Control-Expose-Headers should be added to the headers on server side. Please try adding the following line: res.setHeader('Access-Control-Expose-Headers', '*')
Angular2 's Http.post is not returning headers in the response of a POST method invocation
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers

store unique usersId across conversations with Dialogflow (userStorage ERROR Can't set headers after sent to user)

I am trying to make a Google Home app and I am using webhooks with express.js running on a local server as fulfilment. I would like to use the "request.body.originalDetectIntentRequest.payload.user.userStorage" parameter from the request to identify a user when the webhook sends a post request to my server. The userId given with this request is equal to a log-filename that I store locally for each user where I keep track of different things per user.
I got the idea of handling things like this from an earlier post here on stack overflow. In the answer given by Prisoner, he talks about the importance of the response that you have to send back. So far so good, I tried to implement this in my code by sending a response once the new userId is made but I keep getting the same error :
(node:21236) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:518:11)
So it probably has to do with the fact that I have a line agent.handleRequest(intentMap); at the end of my code which will handle the request and send a response back to the user itself.
How do I solve this? I already tried to find a solution for this problem but the after trying different things, there is only one thing I can think of which would be to expand the response before sending it with the payload.google.userStorage variable even though I have tried response.write but that was giving me the same error.
My code:
app.post('/', express.json(), (request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
let userStorage = request.body.originalDetectIntentRequest.payload.user.userStorage || JSON.stringify({});
let userId;
console.log("userStorage", userStorage);
userStorage = JSON.parse(userStorage);
console.log("userStorage_after_parsing", userStorage);
if (userStorage.hasOwnProperty('userId')) {
userId = userStorage.userId;
console.log('user was already defined:' + userId);
} else {
let uuid = require('uuid/v4');
checkNewUser = true;
userId = uuid();
userStorage.userId = userId
console.log(userId);
response.send({
'payload': {
'google': {
'userStorage': JSON.stringify(userStorage)
}
}
}
);
//rest of the code
agent.handle(intentMap)
UPDATED QUESTION:
After prisoner's answer, I decided to include the code of the handleWelcome intent where I would like to paste the code into to extend the response with the the userStorage variable:
function handleWelcome(agent) {
//code to add userStorage to the response?
if (checkIfUserVerified() === true) {
agent.add('Hi.');
//updateLog('User just said hi.');
let userObj = readJsonSync(userId + '.json');
userObj.logs.push('User just started talking to this chatbot ' + getCurrentDateTime());
fs.writeFileSync(userId + '.json', JSON.stringify(userObj));
}
else {
close('Please make sure you are a verified user before using this version of SMART4MD. To do so, read the SMART4MD Voice assistant manual.');
agent.setFollowupEvent('GOODBYE');
}
}
Note the comment in the answer you cite where it says
// Make sure you include the userStorage as part of the response
"part of" the response - not the response itself. In your code, you're sending back the JSON to sent the user storage before your Intent Handler is called. Once something is sent, the connection is closed, so nothing further can be sent - including the reply you want. Which is why you get the error.
You don't show any of your Intent Handler functions, so it is difficult to give you an exact answer, but the general solution would be to set the userid in userStorage as part of a function that each handler calls.

Error: Can't set headers after they are sent because of res.?

I'm trying to set up a method that is called with Shopify's webhook. I get the data and I'm able to store with a fresh server but I get "Error: Can't set headers after they are sent" returned in the console. I believe this is because I'm calling res twice. Any ideas on how to structure this better?
This is my method:
function createProductsWebHook(req,res,next) {
//if(req.headers){
// res.status(200).send('Got it')
// return next()
// }
res.sendStatus(200)
next()
const productResponse = req.body
console.log(productResponse)
const product = Product.build({
body_html: req.body.body_html,
title: req.body.title,
});
product.save()
.then(saveProduct => res.json(saveProduct))
.catch((e)=> {
console.log(e)
});
}
This occurs because the middleware, createProductsWebHook(), is called first when a request is received, which then sends a 200 status code response, res.sendStatus(200). Then in, in the same middleware function, product.save().then(...) is called. save()’s callback function attempts to send a response too – after one has already been sent by the very same middleware – using res.json(saveProduct).
Key Takeaway
Middleware should not send the response; this defeats the purpose of middleware. Middleware's job is to decorate (add or remove information, i.e, headers, renew some auth session asynchronously, perform side effects, and other tasks) from a request or response and pass it along, like a chain of responsibility, not transmit it – that's what your route handler is for (the one you registered your HTTP path and method with, e.g., app.post(my_path, some_middleware, route_handler).

Can't call next() as last piece of middleware?

For some reason, if I'm the last piece of middleware and I try calling the next() function, I get the same error as this common SO issue.
So that I have a minimal example, I set it up as follows:
In my route, I have:
router.get('/add',(req,res,next) => {
res.render('ideas/add');
next();
});
And then I have a final piece of middleware:
app.use(function finalMiddleware(req,res,next){
console.log("We are here!");
console.log(app._router.stack);
next();
});
And the console logs the following as the last element in the stack:
Layer {
handle: [Function: finalMiddleware],
name: 'finalMiddleware',
params: {},
path: '',
keys: [],
regexp: { /^\/?(?=\/|$)/i fast_star: false, fast_slash: true },
route: undefined } ]
However, I still get:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:471:11)
at ServerResponse.header (../node_modules/express/lib/response.js:767:10)
at ServerResponse.send (../node_modules/express/lib/response.js:170:12)
at done (../node_modules/express/lib/response.js:1004:10)
at Immediate.<anonymous> (../node_modules/express-handlebars/lib/utils.js:26:13)
I'm wondering if this for some reason has to do with handlebars? I just don't understand why if I'm not writing headers with later middleware that this would possibly be an issue. I can't seem to figure out how to debug it further. Sure, I can remove the next() call from my final piece of middleware, but I want to understand if there's something deeper going on as I'm new to Express and want to make I'm understanding stuff correctly.
It turns out I was missing a key piece of information from both the similar answer and from my investigation: if next() is called and nothing is left to execute in regards to middlware, the server will send a message Cannot GET /(route name). For me, my callback was executing after this message was sent, since next() must call the function that writes this default response.
Hence, since Express already sent the message Cannot GET /(routename), as soon as the callback fired, which was after, it wasn't able to send a response.

Handling request simltaneously in nodejs and passing it's response using only one res.send

I am developing an API which takes input in XML containing IDs for media and gives output in XMLform with details of given IDs. I am facing a problem while sending the response of second simultaneous request; here the second request goes into loop showing "loading" on postman.
What I am doing is calling a function in app.post which parses the media and gives output in the callback and send it using res.send, but it works only for single request.
While doing parallel request to same API either it goes in loop or it gives can't set the headers after they are sent as I am using res.send but res.send is the only way which I can use to send the response (even the next doesn't work).
var getCompositeData = function(req, res, next){
abc.getData(req.body, function(err, xmlOutput){
if(err){
console.log("error");
} else {
xmlData = xmlOutput
return next()
}
}
app.post(apiUrl, [
rawBodyParser({
type: 'application/xml'
}),
app.oauth.authorise()
], getCompositeData, function (req, res) {
res.setHeader('Content-Type', 'application/xml');
res.send(xmlData);
});
There are several issues with your code:
if (err) {
console.log("error");
}
If an error occurs, you still need to make sure a response will be sent back, otherwise the request will stall until a timeout happens. You can pass an error to next, and Express will handle it:
if (err) {
return next(err);
}
Next problem:
xmlData = xmlOutput
xmlData is an undeclared variable, which gets overwritten with each request. If two requests happens at (almost) the same time, it's likely that one client gets back an incorrect response (remember, Node.js runs JS code in a single thread; there is not thread-local storage so xmlData gets shared between all requests).
A good place to "store" this sort of data is in res.locals:
res.locals.xmlData = xmlOutput;
return next();
// and later:
res.send(res.locals.xmlData);

Resources