ExpressJS "Error: Can't set headers after they are sent." - javascript

So I'm running into an issue with ExpressJS and can't seem to find documentation to solve the issue.
Tech:
body-parser: 1.17.0
express 4.15.0
multer: 1.3.0
MongoDB
Postman
View is currently 3 fields:
name (required)
tagline (required)
image (optional)
What I'm trying to do is Error Handle on the Image before writing anything into the database. The image can only be of mime type image/jpeg or image/png to prevent HTML being uploaded with malicious JS inside.
I believe the issue seems to be that I'm not properly triggering an error while running through the image check conditionals and sending multiple responses which is setting off the Error: Can't set headers after they are sent.
drinks.routes.js
var express = require('express');
var router = express.Router();
var jwt = require('jsonwebtoken');
var multer = require('multer');
var passport = require('passport');
var config = require('../config/main');
var upload = multer({ dest: 'uploads/images' })
var Drink = require('../models/drinks.model.js');
router.use(function(req, res, next){
next();
});
...
.post(passport.authenticate('jwt', { session: false }), upload.single('image'), function(err, req, res, next){
var drink = req.body;
var drinkImage = req.file;
if(typeof drinkImage !== "undefined"){
console.log('image was uploaded');
if(drinkImage.mimetype !== "image/jpeg" || drinkImage.mimetype !== "image/png" ){
console.log('Image was not a JPEG or PNG', drinkImage.mimetype);
res.status(500).send({ error: "Your image was incorrect"}); // >>>>>>>>>>>>>>> The error seems to be coming from here. Unsure of how to properly raise a flag to tell the response to the client. Have tried res.send(), the res.status().send(), res.json(), currently working with next() method to keep going on but not sure how to define err if that is the case
}
console.log('image correct mimetype');
} else {
drinkImage = {}; // Setting this as an empty object so it doesn't throw an error with the model which is looking for `image: drinkImage.name`
}
Drink.createDrink(drink, drinkImage, function(err, drink, drinkImage){
if(err){
console.log('Error adding Drink', err);
res.send(err);
}
res.status(200).json(drink)
});
});
Topic Research
Express Error Handling
SO Can't Set Headers Twice

This issue is due to Javascript's asynchronous nature.
The code should not execute Drink.createDrink() after responding error 500.
if (drinkImage.mimetype !== "image/jpeg" || drinkImage.mimetype !== "image/png" ) {
console.log('Image was not a JPEG or PNG', drinkImage.mimetype);
res.status(500).send({ error: "Your image was incorrect"});
return; // THIS IS VERY IMPORTANT!
}

Related

Connect node app and server + post image to server

I have a very basic question about a node application, and a question about HTTP requests. It's the first time I create a node app with server, and I just can't seem to get the different components to work together.
This is my server.js
var express = require('express');
var multer = require('multer');
const request = require('request');
const upload = multer({dest: __dirname + '/uploads/images'});
const app = express();
const PORT = 3000;
app.use(express.static('public'));
app.post('/upload', upload.single('photo'), (req, res) => {
if(req.file) {
res.json(req.file);
}
else throw 'error';
});
app.listen(PORT, () => {
console.log('Listening at ' + PORT );
});
Then I have a file app.js with a motion-detection system. Every time motion is detected, a picture is taken. This all works fine.
Then the picture should be sent to the server. This is what I can't figure out.
I created a function toServer() that should post the detected data to the server
const request = require('request');
function toServer(data) {
const formData = {
// Pass data via Buffers
my_buffer: data,
// Pass optional meta-data with an 'options' object with style: {value: DATA, options: OPTIONS}
// Use case: for some types of streams, you'll need to provide "file"-related information manually.
// See the `form-data` README for more information about options: https://github.com/form-data/form-data
};
request.post({url:'http://localhost:3000/upload', formData: formData}, function optionalCallback(err, httpResponse, body) {
if (err) {
return console.error('Upload failed:', err);
}
console.log('Upload successful! Server responded with:', body);
});
};
Problem 1: when running the server.js on localhost:3000, it doesn't find any of the scripts loaded in index.html nor my app.js.
Problem 2: when running the index.html on live-server, all scripts are found, but i get the error "request is not defined".
I am pretty sure there is some basic node setup thing I'm missing.
The solution for toServer() might be more complicated.
Thanks for your time,
Mustard Shaper
Problem 1:
this could happen because you have not specified to render your index.html.
for example:
res.render('index')
if it's not because of the single quotes in upload.single('photo') try double quotes.
Another possible error could be that you are missing a default display engine setting.
an example: https://www.npmjs.com/package/hbs
Problem 2:
it may be because you are missing the header
var request = require('request');
request.post({
headers: {'content-type' : 'application/x-www-form-urlencoded'},
url: 'http://localhost',
body: "example"
}, function(error, response, body){
console.log(body);
});
See more at https://expressjs.com/

cors package send status instead of error

I am trying to use cors package in node and express environment to verify if requesting domain can access my resources. That part was not a problem following official documentation. The problem however is with response. If domain is not allowed to access - cors is sending error with stack trace (paths to files). Can this be avoided and just respond with status 401, something like res.status(401).end() ? I tried that but it gives me error because headers were already sent.
const cors = require("cors");
const corsOptions = async (req, callback) => {
let domain = getDomain(req);
if (domain === false) {
return callback(new Error("Not allowed by CORS"));
}
const isWhitelisted = await client.get(domain).catch(err => { console.log(err); });
if (isWhitelisted !== undefined) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
};
app.use(cors(corsOptions));
So i was hoping to replace callback(new Error("Not allowed by CORS")); this part with just sending status 401 and ending stream so no errors with stack are printed in the client.
It can't be done, according to the source code what you are sending in callback as err is passed in the next function like
if(err) next(err);
But there is one easy workaround to what you want.
The cors() is returning a middleware(a function with three params req, res and next). So you can wrap the cors in your middleware and return whatever you want. If all the things are perfect you can use the cors.
const app = require('express')();
const cors = require("cors");
const corsMid = (req, res, next)=>{
const trueVal = true;
if(trueVal){
res.status(400).send("Error")
}else{
return cors()(req,res,next);
}
}
app.use(corsMid);
app.get('/', (req, res)=>{
res.json('Hello World');
})
app.listen(8080, ()=>{
console.log('started')
})
I found a workaround is you can construct the Error on a variable first, then remove the stack property from the error variable:
let err = new Error('Not allowed by CORS')
err.stack = ""
callback(err)
Hope this help!

How to resolve 404 error when making an image uploaded with reactjs and nodejs?

I'm new to programming and I'm trying to set up an image uploader for my web app. I'm using node.js for the backend and react.js for the front end with mongodb as well. The problem is I'm getting a 404 ERR_NAME_NOT_RESOLVED whenever I try to upload the image.
I've tried a few things like changing the router name for the axios.post in react to no avail. I'm not sure what else to do at this point.
What I think is the relevant code is here:
react (upload-page.js):
fileUploadHandler = () => {
const fd = new FormData();
fd.append('photo', this.state.selectedFile, this.state.selectedFile.name);
axios.post('http://localhost8080/api/images', fd, {
onUploadProgress: progressEvent => {
console.log('Upload Progress:' + Math.round(progressEvent.loaded / progressEvent.total* 100) + '%')
}
})
.then(res => {
console.log(res);
});
}
server.js:
app.use('/api/users/', usersRouter);
app.use('/api/auth/', authRouter);
app.use('/api/images/', uploadRouter);
router.js:
'use strict';
const express = require('express');
const multer = require('multer');
const upload = multer({dest: __dirname + '/uploads/images', storage: multer.memoryStorage() });
const Image = require('./models');
const router = express.Router();
//app.use(express.static('public'));
router.post('/', upload.single('imageField'), (req, res) => {
if(req.file) {
console.error(req.file);
const i = new Image({
data: req.file.buffer
});
i.save().then(() => {
return res.status(201).json({
id: i._id
});
}).catch(e => {
console.error(e);
return res.status(500).json({ message: 'error: ' + e.message})
});
}
else {
throw 'error';
}
});
module.exports = router ;
Full code can be found at these repos:
Backend: https://github.com/beccaww/cats
Frontend: https://github.com/beccaww/cats-client
I expect it to upload the image into the /images/uploads/images on the backend but it just gives me a 404 ERR_NAME_NOT_RESOLVED message instead. Any idea what might be going wrong?
Edit:
Okay, I fixed the axios.post('http://localhost:8080/api/images', fd, {, but now I'm getting a 500 error that says: POST http://localhost:8080/api/images 500 (Internal Server Error), followed by: Uncaught (in promise) Error: Request failed with status code 500. Any idea what might be causing this?
You should include a colon between localhost and the port (Should be localhost:8080, not localhost8080)

How to set the API key in Stampery API.JS file

I am working on setting up Stampery. I am unable to figure out where to set the string API key in this API.JS file. The documentation says to set the STAMPERY_TOKEN as the API key not sure how to do this. Any help would be appreciated.
The link for Stampery is https://github.com/stampery/office.
'use strict';
const express = require('express');
const router = express.Router();
const bodyParser = require('body-parser')
const Stampery = require('stampery');
const development = process.env.NODE_ENV !== 'production';
const stamperyToken = process.env.STAMPERY_TOKEN;
var proofsDict = {}
if (!stamperyToken) {
console.error('Environment variable STAMPERY_TOKEN must be set before running!');
process.exit(-1);
}
//var stampery = new Stampery(process.env.STAMPERY_TOKEN, development ? 'beta' : false);
// For now, always use production Stampery API due to not making it work against beta.
var stampery = new Stampery(process.env.STAMPERY_TOKEN);
router.use(bodyParser.json());
router.post('/stamp', function (req, res) {
var hash = req.body.hash;
// Throw error 400 if no hash
if (!hash)
return res.status(400).send({error: 'No Hash Specified'});
// Transform hash to upper case (Stampery backend preferes them this way)
hash = hash.toUpperCase()
// Throw error 422 if hash is malformed
var re = /^[A-F0-9]{64}$/;
if (!(re.test(hash)))
return res.status(422).send({error: 'Malformed Hash'});
stampery.stamp(hash, function(err, receipt) {
if (err)
res.status(503).send({error: err});
else
res.send({result: receipt.id, error: null});
});
});
router.get('/proofs/:hash', function (req, res) {
var hash = req.params.hash;
stampery.getByHash(hash, function(err, receipts) {
if (err)
res.status(503).send({error: err});
else
if (receipts.length > 0)
res.send({result: receipts[0], error: null});
else
res.status(200).send({error: 'Oops! This email has not yet been attested by any blockchain.'});
});
});
module.exports = router;
I have added the following in Azure website. Should this suffice :
You need to set up STAMPERY_TOKEN environment veriable before starting your server.
You can do this like this for example (in Windows) set STAMPERY_TOKEN=your-token&& node app.js
There are 2 ways to add this to environment (For Ubuntu).
Add to bashrc File. Like:
export STAMPERY_TOKEN="YOUR-TOKEN"
Pass these params before running server. Like:
STAMPERY_TOKEN=YOUR-TOKEN node server.js
To access this variable you can get by:
console.log(process.env["STAMPERY_TOKEN"]);

Express JS + Multer query database before file upload

I'm using Node.JS + Express.JS + Multer to handle file uploads. The problem is that I need to query the database to see if a file with this name has been uploaded in the past. If it hasn't been uploaded, then it should be accepted. Otherwise, the file should not be accepted. I'm trying to get this to work using the onFileUploadStart function; however, the database query is asynchronous and I see no way to return false given that the result of the query appears in a callback. If there is a way to execute the query synchronously, my goal will be easy to accomplish. Here is the code:
var express = require('express');
var router = express.Router();
var mysql = require('mysql');
var connection = mysql.createConnection({
//connection details
});
router.post('/upload', multer({
onFileUploadStart: function(file, req, res) {
var queryString = "SELECT count(fileName) as count FROM table WHERE fileName = ?;",
queryInserts = [file.originalname];
queryString = mysql.format(queryString, queryInserts);
connection.query(queryString, function(err, rows) {
if (err) {
// handle error
} else {
if (rows[0].count > 0) {
// file should not be accepted
} else {
// file should be accepted
}
}
});
},
dest: "./uploads/"
}), function(req, res) {
// do other stuff
});
Any ideas of how I can accomplish this will be greatly appreciated. Thanks.
My quick reaction would be to use promises. You could have your onFileUploadStart handler create a deferred, assign its promise to the active request object and handle the resolution or rejection of the promise. Then in the main handler for the upload route, you could use then.
I believe this would basically be the new code as applied to your current code. I Note that I am using the Q promises library, but there are other options (promises are also built into ES6 if you are using it).
var express = require('express');
var router = express.Router();
var mysql = require('mysql');
var Q = requires('q');
var connection = mysql.createConnection({
//connection details
});
router.post('/upload', multer({
onFileUploadStart: function(file, req, res) {
var deferred = Q.defer();
req.fileUploadPromise = deferred.promise;
var queryString = "SELECT count(fileName) as count FROM table WHERE fileName = ?;",
queryInserts = [file.originalname];
queryString = mysql.format(queryString, queryInserts);
connection.query(queryString, function(err, rows) {
if (err) {
// handle error
deferred.reject('You had an error...');
} else {
if (rows[0].count > 0) {
// file should not be accepted
deferred.reject('You had a duplicate file');
} else {
deferred.resolve(file); // ?? or something useful
// file should be accepted
}
}
});
},
dest: "./uploads/"
}), function(req, res) {
req.fileUploadPromise
.then(function(successResult){
// do other stuff
res.status(200).send('success');
})
.catch(function(errorResult){
// read the error result to provide correct code & error message for user
})
.done();
});

Categories

Resources