Node js - Why lstatSync(Path) returns undefined ? - synchronously check - javascript

I'm new to Node.js , and i'm trying to create a simple server
the problem is:
TypeError: Cannot read property 'isFile' of undefined
what i did so far :
I tried a simple debugging process to find out where the problem exactly
and the problem as i expect in the returning value of lstatSync()
lstatSync() returns undefined all the time , which is a cause the problem in isFile()
Notes :-
I checked in a sample code below the path that i pass to lstatSync() by logging the value in the console and it was as expected
Also after some research i tried to use fs.exists() but i found out that it's deprecated !
Finally the Docs doesn't provide much about the function
Sample Code :
var http = require("http"); // creating server
var path = require("path"); // finding the actual path for directories / files
var url = require("url"); // parse url
var fs = require('fs'); // file system core , dealing with files operations
// Array of mime types ..
var mimeTypes = {
'html' : 'text/html',
'css' : 'text/css',
'js' : 'text/javascript',
'jpg' : 'image/jpg',
'png' : 'image/png',
'jpeg' : 'image/jpeg'
}
// creating server ...
http.createServer(function(req , res){
var uri = url.parse(req.url).pathname // parse url , exctract the path after the host name 'incuding /'
var fileName = path.join(process.cwd(),unescape(uri)); // returing current directory path , unescape the url path in case it contains special char.
console.log("data is loading"+uri);
console.log("File name : "+fileName);
var stats;
try {
stats = fs.lstatSync(fileName) // Returns an instance of fs.Stats.
console.log(stats);
} catch (e) {
console.log(stats);
// if the file not exists [NOT FOUND]
res.writeHead(404,{'Context-Type':'text/plain'});
res.write('Error 404 , page not Found \n');
res.end();
}
// file actual path is a file / directory
// file it's a file
if(stats.isFile()){
var mimeType = mimeTypes[path.extname(fileName).split('.').reverse()[0]]; // file name without extension
res.writeHead(200,{'Content-Type':mimeType});
var readStream = fs.createReadStream(fileName);
readStream.pipe(res);
}else if(stats.isDirectory()){
res.writeHead(302,{
'Location' : 'index.html'
});
res.end();
}else{
res.writeHead(500,{'Content-Type':'text/plain'});
res.write('500 Internal Error \n');
res.end();
}
}).listen(8888);

Calling res.end() doesn't magically stop the rest of the function to run. In the catch handler, you should return from the function explicitly:
try {
stats = fs.lstatSync(fileName) // Returns an instance of fs.Stats.
console.log(stats);
} catch (e) {
console.log(stats);
// if the file not exists [NOT FOUND]
res.writeHead(404,{'Context-Type':'text/plain'});
res.write('Error 404 , page not Found \n');
return res.end();
}
Note that the HTTP server doesn't do anything with the return value of the handler, so return res.end() is just a shortcut for res.end(); return;.

Related

Error: ENOENT: no such file or directory, rename on heroku

I just deployed my first nodejs app to heroku that contains file upload option it working fine in my local host but not in heroku when I checked heroku log it says Error: ENOENT: no such file or directory, rename '/tmp/upload_b7273eba4e1532df2526c225f550de61' -> '/app/routes./uploads/data/1f86e8b13b3e0f95c8ef4d61f8d515ad.pdf'
my code looks like this
routes.post('/',(req,res) => {
var form=new formidable.IncomingForm();
form.parse(req,(err,fields,files) => {
//Check file type
console.log(files.resume.type);
if(files.resume.type !== 'image/jpeg' && files.resume.type !== 'application/pdf' && files.resume.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' && files.resume.type !== 'application/msword'){
res.render('upload',{msg:"Resume must be in jpg,doc,docx,pdf format"})
}else{
//If in resume format
crypto.randomBytes(16,(err,buf) => {
if(err) { return err}
const filename = buf.toString('hex') + path.extname(files.resume.name);
var old_path=files.resume.path;
console.log(old_path);
var new_path=path.join(__dirname+'./uploads/data/' + filename);
console.log(new_path);
fs.rename(old_path,new_path,(err) => {
if(err) throw err;
console.log("File Uploaded");
res.render('upload',{msg:"Upload success"})
})
});
}
})
});
my old_path on heroku is /tmp/upload_b7273eba4e1532df2526c225f550de61 and new_path is /app/routes./uploads/data/1f86e8b13b3e0f95c8ef4d61f8d515ad.pdf
TL;DR:
Put form.uploadDir equal to the desired landing point for your uploads before you use form.parse().
Explanation
I recently ran into this same issue so I decided to post what I found to help others who find their way here.
As Neil Lunn points out, need to set the uploads directory for formidable. The default is os.tempDir(). On Windows this usually looks something like:
C:\Users\ADMINI~1\AppData\Local\Temp\2\
Since this location doesn't always exists be default, it throws an error. You just need to set up a location for your formidable uploads to land, and you have to do it before you parse() the form. For example:
let form = new formidable.IncomingForm();
form.uploadDir = path.join(__dirname, "uploads");
form.parse(req, async function (err, fields, files) {...
This will set your landing point to a directory in the same location as your server.js or App.js file called uploads/.
You can then rename the path of the files you uploaded to match that location and access them there. For Example:
fs.renameSync(files.file.path, "uploads/" + fields.fileName);
Then you can access your file as
fs.readFileSync("uploads/" + fields.fileName);

Sails.js downloading file

I'm beginner in sails.js and I need to add some logic to existing server.
I need to retrieve image from shared network, so I added ImageController.js and action in it:
module.exports = {
download: function(req, res) {
var run = req.param('run');
var test = req.param('test');
var file = req.param('file');
var path = util.format('\\\\tch-smain\\Share\\AutoTest\\Screenshots\\%s\\%s\\%s.jpeg', run, test, file);
fileAdapter.read(path)
.on('error', function (err){
return res.serverError(err);
})
.pipe(res);
}
};
Then I registered route in config\routes.js:
'/img': 'ImageController.download'
But when I trying to execute GET /img?run=a&test=b&file=c I get 500 error Internal Server Error. Something isn't right here.
But file on \\tch-smain\Share\AutoTest\Screenshots\a\b\c.jpeg exists

HTTP/2: stream.end using For-Loop

How can a stream be closed within a for-loop?
I am attempting a HTTP/2 resource push-stream within a loop by reading a .json config file. If there is only one resource in the resurce.file,type, the push is successful. If there are more than one resource in the config, only the first resource listed is pushed and the remaining resource(s) are not pushed & the client never receives the remaining files and will not finish parsing.
I am thinking that only the first resource stream ends and the remaining open streams are not closing.
I commented as best as I believe the code should be functioning:
// Verify if client can be pushed to:
if (res.push) {
// Read what resources should be pushed via config.json5:
for(var i = 0; i < confg.resource.length; i++) {
// Compare what URL requested the resources & pass the relevant files:
if (req.url == confg.resource[i].url) {
// Request the resource(s) to stream:
var ps = fs.readFileSync('../build' + confg.resource[i].file)
// Push the relevant resource stream(s):
, stream = res.push(confg.resource[i].file
, { // ..and set the content type;
res: {'content-type': confg.resource[i].type},
});
// Close the resource stream(s):
stream.end(ps, function() {
// TODO(CelticParser): Remove for production ->v
console.log("Pushed Sream");
});
}
}
}
.config.json5:
resource: [ // HTTP/2 Push-Streams
{ file: '/js/theme.min.js',
type: 'script'
},
{ file: '/css/theme.min.css',
type: 'style',
url: '/'
}
]
Using the example above;
If the /js/theme.min.js is listed first and the /css/theme.min.css is listed second in the config , the /js/theme.min.js will be loaded by the browser and the other file will not load and the client hangs up (doesn't continue parsing). If the the list order of the resources are swapped, the same thing occurs. If there is only one file listed in the config, everything works as expected.
Any help will be greatly appreciated.
The problem is in config.json5. You are checking the requested url before passing the files here:
// Compare what URL requested the resources & pass the relevant files:
if (req.url == confg.resource[i].url) {
...
}
But in your json only one item can pass the test, the other one is missing the url property. Change your config to this (added url: '/' to the first item) and it will work:
{
resource : [
{
file : '/js/theme.min.js',
type : 'script',
url : '/'
},
{
file : '/css/theme.min.css',
type : 'style',
url : '/'
}
]
}
Tested with this small app, following the http2 server example, and also with your server code.
server.js
var fs = require("fs");
var path = require("path");
var http2 = require("http2");
var config = require("./config.json");
var options = {
key : fs.readFileSync("./localhost.key"),
cert : fs.readFileSync("./localhost.crt")
};
//----------------------------------------------------
http2.createServer(options, function(req, res) {
if (res.push) {
config.resource.forEach(function(resource){
if (req.url === resource.url) {
var push = res.push(resource.file, { res: {"content-type": resource.type } });
push.writeHead(200);
fs.createReadStream(path.join("../build", resource.file)).pipe(push);
}
});
}
res.writeHead(200);
res.end();
}).listen(8080);
The config.json file contains the corrected config described above.
Output:
Receiving pushed resource: ./js/bootstrap.min.js -> /Developer/so/tests/push-0
Receiving pushed resource: ./css/bootstrap.min.css -> /Developer/so/tests/push-1

Can't set headers after they are sent error from Express middleware

I have a Node Express app and have written a bit of middleware to do the following:
Check all incoming requests if it's from a bot
Allow requests for raw resources through
If requests are from a bot, server an HTML snapshot
I'm getting the following error from the code below:
Error: Can't set headers after they are sent.
I'm not sure why this is happening, can anyone help?
var snapshotDir = require("path").resolve(__dirname + "/../snapshots");
var bots = [
/facebookexternalhit/,
/googlebot/
];
var resources = [
/\/framework\//,
/\/partials\//,
/\/views\//
];
app.use(function(req, res, next){
//test user-agent against bots array
var isBot = function(agent){
return bots.some(function(bot){
return bot.test(new RegExp(agent));
});
}
//test path for raw resources
var isResource = function(path){
return resources.some(function(resource){
return resource.test(new RegExp(path));
});
}
//check request type
if (isResource(req.url)) return next(); //request is for raw resource
if (!isBot(req.get("user-agent")) && !/\?_escaped_fragment_=/.test(req.url)) return next(); //user-agent is not bot
//get url path without escaped fragment
var path = req.url.replace("?_escaped_fragment_=", "");
//format path into filename
if (path.charAt(0) !== "/") path = "/" + path; //prepend fragment with '/'
if (path === "/") path = "/index.html"; //home requested: serve index.html
if (path.indexOf(".html") == -1) path += ".html"; //append fragment with '.html'
//serve snapshot file
try { res.sendFile(snapshotDir + path); } //serve html snapshot
catch (err) { res.send(404); } //no snapshot available, serve 404 error
//next request
return next();
});
You can't write twice res.send for one request (which you are doing here if the first statement fails and goes to the catch block)
try { res.sendFile(snapshotDir + path); } //serve html snapshot
catch (err) { res.send(404); } //no snapshot available, serve 404 error
You should try to understand why the error occurs, then do a check yourself before sending the file.
Probably the file doesn't exist (you can check that by using fs.statSync

Node.js - Reading CSV-file not working with line numbers > 500

I am currently struggling to run my Node.js server.
What I want to do:
Upload a CSV-File from mobile device to my local server and save it on the file system
Read each line of the .csv-File and save each row to my MongoDB database
Uploading and saving the file works flawlessly. Reading the .csv-File and saving each row to the database only works for files with small line numbers.
I don't know the exact number of lines when it stops working. It seems to differ every time I read a file.
Sometimes (if the line numbers are bigger than 1000) the CSV-Reader I use doesn't even start processing the file. Other times he reads only 100-200 lines and then stops.
Here is my code how I upload the file:
var fs = require('fs');
var sys = require("sys");
var url = require('url');
var http = require('http');
http.createServer(function(request, response) {
sys.puts("Got new file to upload!");
var urlString = url.parse(request.url).pathname;
var pathParts = urlString.split("/");
var deviceID = pathParts[1];
var fileName = pathParts[2];
sys.puts("DeviceID: " + deviceID);
sys.puts("Filename: " + fileName);
sys.puts("Start saving file");
var tempFile = fs.createWriteStream(fileName);
request.pipe(tempFile);
sys.puts("File saved");
// Starting a new child process which reads the file
// and inserts each row to the database
var task = require('child_process').fork('databaseInsert.js');
task.on('message', function(childResponse) {
sys.puts('Finished child process!');
});
task.send({
start : true,
deviceID : deviceID,
fileName : fileName
});
sys.puts("After task");
response.writeHead(200, {
"Content-Type" : "text/plain"
});
response.end('MESSAGE');
}).listen(8080);
This works all fine.
Now the code of the child process (databaseInsert.js):
var sys = require("sys");
var yaCSV = require('ya-csv');
var Db = require('mongodb').Db;
var dbServer = require('mongodb').Server;
process.on('message', function(info) {
sys.puts("Doing work in child process");
var fileName = info.fileName;
var deviceID = info.deviceID;
sys.puts("Starting db insert!");
var dbClient = new Db('test', new dbServer("127.0.0.1", 27017, {}), {
w : 1
});
dbClient.open(function(err, client) {
if (err) {
sys.puts(err);
}
dbClient.createCollection(deviceID, function(err, collection) {
if (err) {
sys.puts("Error creating collection: " + err);
} else {
sys.puts("Created collection: " + deviceID);
var csvReader = yaCSV.createCsvFileReader(fileName, {
columnsFromHeader : true,
'separator' : ';'
});
csvReader.setColumnNames([ 'LineCounter', 'Time', 'Activity',
'Latitude', 'Longitude' ]);
var lines = 0;
csvReader.addListener('data', function(data) {
lines++;
sys.puts("Line: " + data.LineCounter);
var docRecord = {
fileName : fileName,
lineCounter : data.LineCounter,
time : data.Time,
activity : data.Activity,
latitude : data.Latitude,
longitude : data.Longitude
};
collection.insert(docRecord, {
safe : true
}, function(err, res) {
if (err) {
sys.puts(err);
}
});
});
}
});
});
process.send('finished');
});
At first I didn't use a child process but I had the same behaviour as I have now. So I tested this.
Hopefully someone who has some experience with Node.js can help me.
I think your issue is that you are trying to read the tempFile while it is still being written to. Right now you are piping the request to the file stream (which proceeds in parallel and asynchronously) and start the reader process. The reader process will then start reading the file in parallel with the write operations. If the reader is faster (it usually will be), it will read the first couple of records but then encounter an end of file and stop reading.
To remedy this, you could only start the reader process after writing has completely finished, i.e., put the part from sys.puts("File.send"); onward into a callback of tempFile.end(...) (see http://nodejs.org/api/stream.html#stream_writable_end_chunk_encoding_callback).
Reading the file while it is still being written to, akin to the tail command in Unix, is fairly hard in my understanding (google for details on how difficult it is to implement a proper tail).
Are you familiar with mongoimport/export?
I used this in the past to export from my db to a csv file...so you can do the opposite after you upload it from the mobile-client to the server.
Its from the shell, but you can write it in code using nodeJS_ChildSpawn

Categories