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();
});
Related
I am working on a management system, currently creating the POST requests for the api. I need the values of the request's body to post a new city in the database. The values are used in the stored procedure as parameters. Instead of the key's values which I entered, I am getting an "undefined" value, sometimes a "[object Object]".
I am using a MySQL server, hosted in the cloud by Google's services. Backend is made with Node.js, routing with Express. None of my attempts to fix the bug worked for me.
What I've tried so far:
-Parsing each key's value .toString() and JSON.stingfify() from the body
-Converting the body to Json/string, then back to a javascript object
-Getting the response's requests's body (res.req.body)
-Getting the body's response values in an array, then pushing the next element after it has been passed as a parameter to the stored procedure
-Using the 'request' npm extension to put the values I need when calling the POST method.
-Changed the values to be stored in the URL' parameters instead of the body.
-Sent the body's values as form-data, JSON file, HTML page
Controller method in cityController.js:
exports.city_post = (req, res, next)=>{
poolDb.getConnection(function (err, connection){
if(!err) {
const sql = 'CALL createNewCity(?,?)';
var zipReq = req.params.zip;
var nameReq = req.params.name;
var reqBody = JSON.stringify(req.res.body);
connection.query(sql,[zipReq,nameReq], (err,rows)=>{
if(!err){
return res.status(201).json({Message: 'City with name: '+nameReq+' and zip code: '+zipReq+' created successfully\n', rows});
}
else{
return res.status(404).json({errorMessage: err})
}
});
}
else{
return res.status(500).json({errorMessage: "server error: "+this.err});
}
console.log("\nZip Code: "+ zipReq +"\nName: " + nameReq); //for testing
console.log("\nrequest body: " + reqBody); //for testing
});
}
City route:
const express = require('express');
const router = express.Router();
const CityController = require('../controllers/cityController.js');
router.get('/', CityController.city_getAll);
router.get('/:cityzip', CityController.city_getbyzip);
router.post('/add', CityController.city_post);
...
module.exports = router;
Expected: Posting a new field in table city, status code (201).
Actual: Status code (404), no new insertion in the DB. body, req.body.zip & req.body.name are of value "undefined".
Screenshots:
-Terminal output: https://imgur.com/a/brqKZlP
-Postman request: https://imgur.com/a/ZfFcX8Z
Express doesn't parse post body by default (for some reason). You can try popular body-parser npm package, or collect the raw body data and parse from a string yourself if you don't want to add a whole new package. Here as express app:
app.use(function(req, res, next){
var data = "";
req.on('data', function(chunk){ data += chunk})
req.on('end', function(){
req.rawBody = data;
var json = JSON.parse(req.rawBody); // assuming valid json. ideally check content-type first
next();
});
});
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.
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 have a Node.js / Express app working, that receives routes like so:
app.get('/resource/:res', someFunction);
app.get('/foo/bar/:id', someOtherFunction);
This is great and works fine.
I am also using Socket.IO, and want to have some server calls use websockets instead of traditional RESTful calls. However, I want to make it very clean and almost use the same syntax, preferrably:
app.sio.get('/resource/:res', someFunction);
This will give a synthetic 'REST' interface to Socket.IO, where, from the programmer's perspective, he isn't doing anything different. Just flagging websockets: true from the client.
I can deal with all the details, such as a custom way to pass in the request verbs and parse them and so and so, I don't have a problem with this. The only thing I am looking for is some function that can parse routes like express does, and route them properly. For example,
// I don't know how to read the ':bar',
'foo/:bar'
// Or handle all complex routings, such as
'foo/:bar/and/:so/on'
I could dig in real deep and try to code this myself, or try to read through all of express' source code and find where they do it, but I am sure it exists by itself. Just don't know where to find it.
UPDATE
robertklep provided a great answer which totally solved this for me. I adapted it into a full solution, which I posted in an answer below.
You can use the Express router class to do the heavy lifting:
var io = require('socket.io').listen(...);
var express = require('express');
var sioRouter = new express.Router();
sioRouter.get('/foo/:bar', function(socket, params) {
socket.emit('response', 'hello from /foo/' + params.bar);
});
io.sockets.on('connection', function(socket) {
socket.on('GET', function(url) {
// see if sioRouter has a route for this url:
var route = sioRouter.match('GET', url);
// if so, call its (first) callback (the route handler):
if (route && route.callbacks.length) {
route.callbacks[0](socket, route.params);
}
});
});
// client-side
var socket = io.connect();
socket.emit('GET', '/foo/helloworld');
You can obviously pass in extra data with the request and pass that to your route handlers as well (as an extra parameter for example).
robertklep provided a great answer which totally solved this for me. I adapted it into a full solution, which is below in case others want to do something similar:
Node (server side):
// Extend Express' Router to a simple name
app.sio = new express.Router();
app.sio.socketio = require('socket.io').listen(server, { log: false });
// Map all sockets requests to HTTP verbs, which parse
// the request and pass it into a simple callback.
app.sio.socketio.sockets.on('connection', function (socket) {
var verbs = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
for (var i = 0; i < verbs.length; ++i) {
var go = function(verb) {
socket.on(verb, function (url, data) {
var route = app.sio.match(verb, url);
if (route && route.callbacks.length) {
var req = {url: url, params: route.params, data: data, socket:socket}
route.callbacks[0](req);
}
});
}(verbs[i]);
}
});
// Simplify Socket.IO's 'emit' function and liken
// it traditional Express routing.
app.sio.end = function(req, res) {
req.socket.emit('response', req.url, res);
}
// Here's an example of a simplified request now, which
// looks nearly just like a traditional Express request.
app.sio.get('/foo/:bar', function(req) {
app.sio.end(req, 'You said schnazzy was ' + req.data.schnazzy);
});
Client side:
// Instantiate Socket.IO
var socket = io.connect('http://xxxxxx');
socket.callbacks = {};
// Similar to the server side, map functions
// for each 'HTTP verb' request and handle the details.
var verbs = ['get', 'post', 'put', 'path', 'delete'];
for (var i = 0; i < verbs.length; ++i) {
var go = function(verb) {
socket[verb] = function(url, data, cb) {
socket.emit(String(verb).toUpperCase(), url, data);
if (cb !== undefined) {
socket.callbacks[url] = cb;
}
}
}(verbs[i]);
}
// All server responses funnel to this function,
// which properly routes the data to the correct
// callback function declared in the original request.
socket.on('response', function (url, data) {
if (socket.callbacks[url] != undefined) {
socket.callbacks[url](data);
}
});
// Implementation example, params are:
// 1. 'REST' URL,
// 2. Data passed along,
// 3. Callback function that will trigger
// every time this particular request URL
// gets a response.
socket.get('/foo/bar', { schnazzy: true }, function(data){
console.log(data); // -> 'You said schnazzy was true'
});
Thanks for your help, robertklep!
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