I have a simple example:
server = http.createServer(function(req, res) {
uploads = {}
if (req.url == '/check') {
res.writeHead(200, {'content-type': 'text/plain'});
res.write(JSON.stringify(uploads));
res.end();
}
if (req.url == '/upload') {
var form = new formidable.IncomingForm();
form.on('fileBegin', function(field, file) {
var tracker = {file: file, progress: [], ended: false};
uploads[file.filename] = tracker;
file.on('progress', function(bytesReceived) {
tracker.progress.push(bytesReceived);
})
});
};
});
Why "check" return empty uploads ? Should I use global variable in this case ?
Your problem has nothing to do with global variables - when a new variable is created in JavaScript without being prefixed by var, it is dynamically scoped AFAIK, which means it is global. Anyway, I would feel it better if your variable was indeed declared outside the function for not only being global, but for looking global too:
var uploads = {}
server = http.createServer(function(req, res) {
if (req.url == '/check') {
// ...
Now, let us see your real problem: you are using formidable in a very strange way. I, for one, have never used the on() method of formidable.IncomingForm. I would recommend you to use the parse() method, which provides you with a key-value object where the keys are the name of the <input type="file"> field and the value is the upload file data, such as the path of the file in the server:
if (req.url == '/upload') {
var form = new formidable.IncomingForm();
form.parse(req, function (err, fields, files) {
for (var filename in files) {
var file = files[filename];
var tracker = {filename: filename, progress:[], ended:false};
fs.readFile(file.path, function(err, filecontent) {
tracker.progress.push(filecontent);
tracker.ended = true;
});
uploads[filename] = tracker;
}
res.writeHead(200, {'content-type': 'text/html'});
res.write("<A href='/'>ok</a>");
res.end();
});
IncomingForm.parse() will process all the files before call the callback. You will have all the files available, and the path to them. Of course, in this example our tracker.progress will contain only one bunch of data - which will be the complete file content.
Also, note that the form you will use to upload the file should have the enctype='multipart/form-data' attribute:
<form action='/upload' method='POST' enctype='multipart/form-data'>
That is it, hope this helps. Note that I am assuming that you need to solve a problem; if you want to test the IncomingForm.on() method, however, this answer will not be much useful. I hope I have made the correct guess :)
upload is being set to an empty object({}) on every request, its always going to be {}.
Note that the function is called on all requests
Global variable has a special meaning in node.js since all modules has a local scope.
The solution would be to declare it outside of the function.
Like this.
var uploads = {};
var server = http.createServer(function(req, res) {
....
I hope this helps
Related
I have a nodejs route where I am trying to download a url as mp3 using npm-youtube-dl. I have a download directory that I watch with chokidar for files being added and when a file is added I save the link to the file and after the download finishes I call a function that's supposed to respond with the download URL using res.download. When the sendURL function is called the url that I can clearly see has been saved before is undefined when I console.log it... Any idea what i'm doing wrong here/how I can fix this? i'm guessing it's a js variable scope issue?
my code:
var express = require('express');
var router = express.Router();
var yt = require('youtube-dl');
var fs = require('fs');
var path = require('path');
var chokidar = require('chokidar');
var downloadPath = '';
var watcher = chokidar.watch('./downloads', {
ignored: '/^[^.]+$|\.(?!(part)$)([^.]+$)/',
persistent: true
});
watcher.on('add', function(path) {
console.log('added: ', path);
this.downloadPath = path;
console.log('saved', this.downloadPath);
});
/*
router.get('/', function(req, res, next) {
next();
});
*/
router.get('/', function(req, res) {
var url = 'https://soundcloud.com/astral-flowers-music/bella-flor';
var options = ['-x', '--audio-format', 'mp3', '-o', './downloads/%(title)s.%(ext)s'];
var video = yt.exec(url, options, {}, function exec(err, output) {
if (err) { throw err; }
console.log(output.join('\n'));
sendUrl();
});
function sendUrl() {
console.log(this.downloadPath);
//res.download(this.downloadPath);
}
});
module.exports = router;
You're misusing this. If you want to use the downloadPath variable in your functions, remove the this. from in front of them. this.downloadPath looks for a property called downloadPath on an object referenced by this, which is different from your module-global variable.
More: How does the "this" keyword work?
Even with that, you're relying on your add callback having been called before any client requests your / route, and you're returning the last value assigned to downloadPath by that add callback. I don't know enough about what you're doing to know whether that's correct, but the lack of coordination seems problematic.
I made a listing of files that exist in a particular folder, I would like that after listing, it would be possible to access this variable in HTML.
Server.js
var http = require('http');
var arquivo = require('fs');
var server = http.createServer(function(request, response){
response.writeHead(200, {'Content-Type': 'text/html'});
arquivo.readFile(__dirname+'/pagina.html',
function(err, html){
if (err) {
response.write("Arquivo não encontrado!");
response.end();
} else{
response.write(html);
var fs = require('fs');
var files = fs.readdirSync(__dirname+'/list/'); //LIST FOLDER
response.end();
}
});
});
server.listen(3000, function(){
console.log('Servidor está rodando!');
});
pagina.html
<html>
<script type="text/javascript">
alert(files); //Name of var list
</script>
</hmtL>
Replacing a raw string with a variable has several clean solutions. I would recommend any of the following:
handlebars
ejs
hogan
There are two ways to do this.
separate your call into another endpoint on your server and make a request on the client (recommended since you will need this later probably)
use regex to change your html file (fun experiment)
imagine your html file looks like this; notice the double brackets.
"<html><body><script> var files = {{myvars}} </script></body></html>"
and you have a list of files in an array
var yourFiles = ['file1', 'file2', 'file3']
you can then use regex to fill in the variables in the original html string
var newHTML = html.replace('/\{\{myvars\}\}/', JSON.stringify(yourFiles))
response.write(newHTML);
response.end();
the final html sent will look like this
"<html><body><script> var files = ['file1', 'file2', 'file3'] </script></body></html>"
For my api I want to require a separate file to set a cookie (using cookie-parser). However res or req are not passed to the required file...
index.js
app.get('/api/user/:username', function(req, res) {
urlUsername = req.params.username;
require('./set/cookie')
});
set/cookie.js
res.cookie('login_session', urlUsername) // returns 'res' not defined
As you can see to partially overcome this problem I set urlUsername which works. But surely there has to be another way :) ?
Thanks
you need to modify your code like this
======= set/cookie.js ==========
module.exports = function(res) { // accept res parameter
res.cookie('login_session', urlUsername)
};
=========== index.js ===========
app.get('/api/user/:username', function(req, res) {
urlUsername = req.params.username;
require('./set/cookie')(res); // pass res to module
});
you need to use "module.exports" in the module you created as require returns an object with the same name.
You need to modify your cookie.js file so that it creates an object that has the ability to cache, and that you can pass 'res' object to, so that it is available in scope
For instance, your cookie.js should look like the following:
module.exports.cookie = function(res){
return {
cache: function(){
/*your cookie code*/
return res.cookie('login_session', urlUsername);
}
};
}
This returns an object that has a cache method, and that has the response object passed to it. You would then invoke this by calling:
app.get('/api/user/:username', function(req, res) {
urlUsername = req.params.username;
var cookie = require('./set/cookie').cookie(res);
cookie.cache();
});
How would I go about creating a one time download link in nodeJS or Express?
I'm trying to find the simplest way to accomplish this. My ideas so far are:
Use fs stream to read and then delete the file
or
Somehow generate a link/route that gets removed once the download button is clicked
Are any of these implementations possible?
Is there a simpler way?
Any help or example code would be greatly appreciated!
-Thanks
Check this simple implementation:
You store the information of the download in a file. The filename is the download session id. The file content is the real path of the file to be downloaded.
Use these three functions to manage the lifecycle of the download sessions:
var fs = require('fs');
var crypto = require('crypto');
var path = require('path');
// Path where we store the download sessions
const DL_SESSION_FOLDER = '/var/download_sessions';
/* Creates a download session */
function createDownload(filePath, callback) {
// Check the existence of DL_SESSION_FOLDER
if (!fs.existsSync(DL_SESSION_FOLDER)) return callback(new Error('Session directory does not exist'));
// Check the existence of the file
if (!fs.existsSync(filePath)) return callback(new Error('File doest not exist'));
// Generate the download sid (session id)
var downloadSid = crypto.createHash('md5').update(Math.random().toString()).digest('hex');
// Generate the download session filename
var dlSessionFileName = path.join(DL_SESSION_FOLDER, downloadSid + '.download');
// Write the link of the file to the download session file
fs.writeFile(dlSessionFileName, filePath, function(err) {
if (err) return callback(err);
// If succeeded, return the new download sid
callback(null, downloadSid);
});
}
/* Gets the download file path related to a download sid */
function getDownloadFilePath(downloadSid, callback) {
// Get the download session file name
var dlSessionFileName = path.join(DL_SESSION_FOLDER, downloadSid + '.download');
// Check if the download session exists
if (!fs.existsSync(dlSessionFileName)) return callback(new Error('Download does not exist'));
// Get the file path
fs.readFile(dlSessionFileName, function(err, data) {
if (err) return callback(err);
// Return the file path
callback(null, data);
});
}
/* Deletes a download session */
function deleteDownload(downloadSid, callback) {
// Get the download session file name
var dlSessionFileName = path.join(DL_SESSION_FOLDER, downloadSid + '.download');
// Check if the download session exists
if (!fs.existsSync(dlSessionFileName)) return callback(new Error('Download does not exist'));
// Delete the download session
fs.unlink(dlSessionFileName, function(err) {
if (err) return callback(err);
// Return success (no error)
callback();
});
}
Use createDownload() to create download sessions wherever you need to. It returns the download sid, then you can use it to build your download URL like: http://your.server.com/download?sid=<RETURNED SID>.
Finally you can add a simple handler to your /download route:
app.get('/download', function(req, res, next) {
// Get the download sid
var downloadSid = req.query.sid;
// Get the download file path
getDownloadFilePath(downloadSid, function(err, path) {
if (err) return res.end('Error');
// Read and send the file here...
// Finally, delete the download session to invalidate the link
deleteDownload(downloadSid, function(err) {
// ...
});
});
});
With this method, you don't have to create/move/delete big download files, which could cause slow responses and unnecessary resource consumption.
You can delete routes from the app.routes object. See Remove route mappings in NodeJS Express for more info.
Here is my quick and not very well tested way of doing what you ask:
var express = require('express');
var app = express();
app.get('/download', function(req,res,next){
res.download('./path/to/your.file');
//find this route and delete it.
for(i = 0; i < app.routes.get.length; i++){
if(app.routes.get[i].path === '/download'){
app.routes.get.splice(i,1);
}
}
});
app.listen(80);
I'd probably map a single route to manage downloads, and then upon downloading the file, move or delete it. That way I can prevent a lot of cashing of routes, or a lot of small temp files from the other two answers, but YMMV. Something like this:
// say your downloads are in /downloads
app.get('/dl/:filename', function(req, res) {
var fileStream = fs.createReadStream('/downloads' + req.params.filename);
// error handler, ie. file not there...
fileStream.on('error', function(err) {
if(err) {
res.status(404); // or something
return res.end();
}
});
// here you ow pipe that stream to the response,
fileStream.on('data', downloadHandler);
// and here delete the file or move it to other folder or whatever, do cleanup
fileStream.on('end', deleteFileHandler);
}
Note: This is a possible security vulnerability, it could let the adversary download files outside your downloads location. That filename param is passed directly to fs.
I am trying to append HTML dynamically with Express framework in a static HTML file that my server serves. I've found about the cheerio module that does exactly what I want, but I was wondering if there is a much cheaper way for the system instead of loading the whole HTML and appending a string.
I searched about AJAX and how to communicate with the client but I didn't manage to make it work. The code I am using with cheerio is:
exports.modify = function(req, res){
var html = fs.readFileSync(__dirname + '/../public/index.html', 'utf8');
var $ = cheerio.load(html);
var scriptNode = '<p>Source code modified</p>';
$('body').append(scriptNode);
fs.writeFile(__dirname + '/../public/index.html', $.html(), function (err) {
if (err) throw err;
console.log('It\'s modified!');
});
res.send($.html());
};
How can I do it in more 'proper' way (maybe with AJAX call)? Any suggestions would be more than welcome.
Assuming you want to handle JSON as a data type then you can setup another specific route or you can filter the request type within the current route handler :
exports.index = function(req, res) {
var data = someData.fetch();
switch(req.format) {
case 'json':
res.json(data);
break;
default:
res.render('template', {
data:data
});
}
};