I am building my first Node.js MVC app (native node, not using Express) and having trouble displaying images from my html files via relative their paths.
I'll spare you my server.js and router.js code, but here is my controller code which is basically how Im loading my views:
var fs = require("fs");
var controller = require("controller");
var load_view = 'view_home';
var context = { foo : 'bar' };
controller.load_view(res, req, fs, load_view, context);
this gets passed to...
var url = require('url');
var Handlebars = require('handlebars');
function load_view(res, req, fs, view, context, session){
// Files to be loaded, in order
var files = [
'view/elements/head.html',
'view/elements/header.html',
'view/' + view +'.html',
'view/elements/footer.html'
];
// Start read requests, each with a callback that has an extra
// argument bound to it so it will be unshifted onto the callback's
// parameter list
for(var i=0; i<files.length; ++i)
fs.readFile(files[i], handler.bind(null, i));
var count = 0;
function handler(index, err, content) {
// Make sure we don't send more than one response on error
if(count < 0) return;
if(err) {
count = -1;
console.log('Error for file: ' + files[index]);
console.log(err);
res.writeHead(500);
return res.end();
}
// Reuse our `files` array by simply replacing filenames with
// their respective content
files[index] = content;
// Check if we've read all the files and write them in order if
// we are finished
if(++count===files.length) {
res.writeHead(200, { 'Content-Type': 'text/html' });
for(var i = 0; i < files.length; ++i) {
var source = files[i].toString('utf8');
// Handlebars
var template = Handlebars.compile(source);
var html = template(context);
res.write(html); /* WRITE THE VIEW (FINALLY) */
}
res.end();
}
} // handler()
} // load()
Finally, here is my html with tag and relative path in the src attribute:
<div class="help">
<img src="../public/img/test.jpg" />
</div>
My file system as it relates to the above is as follows:
I am certain the relative path is correct but have tried all combinations of a relative path and even an absolute path. Still, no image is displayed.
Coming from a LAMP background, accessing images in the file tree is trivial but I realize the server is set up much differently here (since I was the one who set it up).
I access other files (like stylesheets) in the filesystem by creating a separate controller to load those files explicitly and access that controller using a URI. But this approach in impractical for an indeterminate number of images.
How do I access & display my images in the filesystem with Node.js?
I access other files (like stylesheets) in the filesystem by creating a separate controller to load those files explicitly and access that controller using a URI.
You need to have a controller. That's how the code translates the URL that the browser requests into a response.
But this approach in impractical for an indeterminate number of images.
Write a generic one then. Convert all URLs that don't match another controller, or which start with a certain path, or which end with .jpg, or whatever logic you like into file paths based on the URL.
The read the contents of the file and output a suitable HTTP header (including the right content-type) followed by the data.
Related
My app is created with mean and I am a user of docker too. The purpose of my app is to create and download a CSV file. I already created my file, compressed it and placed it in a temp folder (the file will be removed after the download). This part is in the nodejs server side and works without problems.
I already use several things like (res.download) which is supposed to download directly the file in the browser but nothing append. I tried to use blob in the angularjs part but it doesn't work.
The getData function creates and compresses the file (it exists I can reach it directly when I look where the app is saved).
exports.getData = function getData(req, res, next){
var listRequest = req.body.params.listURL;
var stringTags = req.body.params.tagString;
//The name of the compressed CSV file
var nameFile = req.body.params.fileName;
var query = url.parse(req.url, true).query;
//The function which create the file
ApollineData.getData(listRequest, stringTags, nameFile)
.then(function (response){
var filePath = '/opt/mean.js/modules/apolline/client/CSVDownload/'+response;
const file = fs.createReadStream(filePath);
res.download(filePath, response);
})
.catch(function (response){
console.log(response);
});
};
My main problem is to download this file directly in the browser without using any variable because it could be huge (like several GB). I want to download it and then delete it.
There is nothing wrong with res.download
Probably the reason why res.download don't work for you is b/c you are using AJAX to fetch the resource, Do a regular navigation. Or if it requires some post data and another method: create a form and submit.
I am evaluating feathers.js for a project. I like its aspirations. So, I decided to try and build a basic content management system just as a learning endeavor. Things have gone pretty smoothly. However, I want to load some static content (articles) into memory when the app starts. I can't figure out how to do that.
I have my articles in the data/articles directory. Each article is markdown named [title].md. I have a block of JavaScript that I tested in a console app that converts the markdown to HTML. That code uses markdown-js to get the HTML into a JSON object. It looks like this:
const fs = require('fs');
const markdownConverter = require('markdown');
let articles = [];
let files = fs.readdirSync('./data/articles');
for (let i=0; i<files.length; i++) {
let title = files[i].substr((files[i].lastIndexOf('/')+1), (files[i].length-3));
let markdown = fs.readFileSync(files[i], 'utf8');
let html = markdownConverter.toHTML(markdown);
articles[title] = html;
}
I've added a route in Feathers that works like this:
app.use('/articles/:slug', function(req, res) {
console.log('loading article: ' + req.params.slug);
let content = '';
// TODO: How to get access to the articles array.
// I want to get the HTML using content = articles[req.params.slug];
res.render('article', { content: content });
});
I'm not sure where to put the code that loads the markdown into an array that I can access when a user requests an article. Where does that belong? My guess is in the app.js file that gets generated when you create a Feathers project using the yeoman generator. Yet, I'm not sure what that actually looks like.
Since feathers is an Express app, you should be able to use express middleware. I recommend this one that allows you to create HTML templates for you markdown and serve them statically without creating any parsers or for loops.
https://github.com/natesilva/node-docserver
var docserver = require('docserver');
...
app.use(docserver({
dir: __dirname + '/docs', // serve Markdown files in the docs directory...
url: '/'} // ...and serve them at the root of the site
));
Or, this middleware that will preParse the Markdown before serving it as HTML in a Jade template.
https://www.npmjs.com/package/markdown-serve
I have a rather complex setup which requires the web browser local storage has the computer's name populated in order for the application to work properly. In order to do this I read from a configuration file:
kiosk-name: Mort
I read the config file when I start my node.js web server:
var filesys = require('fs');
var os = require('os');
filesys.readFile(project_path + '/kiosk.cfg', 'utf8', function(err, data) {
var kioskname;
if (err) {
//console.log(err);
kioskname = os.hostname();
} else {
var configArray = data.split(':');
if('' != configArray[1]) {
kioskname = configArray[1];
} else {
kioskname = os.hostname();
}
}
});
All of this works as designed, using the computer's os.hostname() as a default when the config file is not populated.
The client side features a base page (index.html) which loads a default page (default.html) into an iframe. Based on a websocket messaging system the default page gets replaced by another page from a remote IP. In an older version of the system (prior to implementing a config file) we were able to set the local storage element with the following code:
var win = document.getElementsByTagName('iframe')[0].contentWindow;
win.postMessage(JSON.stringify({key: 'kiosk-name', data: kioskName}), "*");
We identify the iframe when the websocket message is received and then send a post message containing a JSON string to set the local storage element. In this case kioskName is a variable containing a hard-coded value.
The Problem
Now that we wish to read values from a config file we need a way to pass kioskname out to the client-side JavaScript so we can set the local storage element in the iframe.
I attempted putting the file reading function in an export wrapper:
(function(exports){
// file reading code here
return kioskname;
})(typeof exports === 'undefined' ? this['kioskname']={} : exports);
I got an error:
Uncaught ReferenceError: require is not defined
Placing a static value in the export function (with out the require's allows the export function to work properly, but doesn't allow me to read the config file which requires both the os and fs modules.
How do I get the value returned from the config file to a place where I can use it on the client-side to set a local storage element?
This is a creative solution which may not be suitable for every case as it involves utilizing a websocket between the Node.js web server and the client.
Websocket setup to send to client (assumes webserver at 'node_server':
var io = require('socket.io').listen(node_server); // 'attaches' socket.io to this web server
io.sockets.on('connection', function (socket) {
socket.emit('message', 'socket.io connected'); // output a connection message
// receive JSON message and send to the websocket
socket.on('message', function (data) {
var address = node_server.address();
var client = dgram.createSocket("udp4");
var message = new Buffer(data);
// out of the airlock!
client.send(message, 0, message.length, address.port, address.address, function(err, bytes) {
client.close();
});
});
});
Read the config file, then parse and send a message to the socket (done on the server-side):
filesys.readFile(project_path + '/kiosk.cfg', 'utf8', function(err, data) {
var kioskname;
if (err) {
//console.log(err);
kioskname = os.hostname();
} else {
var configArray = data.split(':');
if('' != configArray[1]) {
kioskname = configArray[1];
} else {
kioskname = os.hostname();
}
}
// create JSON string for transmission
KioskName = JSON.stringify({'config':{'kiosk-name': kioskname}});
var send_KioskName = setInterval(function(){ // could be a setTimeout for a one time send
io.sockets.emit('message', KioskName.toString()); // send config file data to browser via socket
}, 30000);
});
NOTE this can be expanded to send multiple pieces of data via JSON to the client should the need arise. A couple of small edits are all that is needed to setup a more detailed JSON object.
Receive the socket message on the client side (this code is loaded by the client), then parse. The resulting object is added to the namespace for this application, making the object available to multiple scripts when required.
CAUTION: You should only use this methodology for objects which do not interfere with objects you may create or destroy in your scripts along the way.
// make sure a string is JSON before using
function isJSON(str) {
try {
JSON.parse(str);
return true;
} catch (e) {
return false;
}
}
// set up object 'array's
var kioskname = {};
// connect a socket to listen for incoming messages from the Big Giant Head
var socket = io();
socket.on('message', function (data) {
if(isJSON(data)) {
// parse the json
var json = $.parseJSON(data);
// determine how to use this JSON object, multiple objects may be sent
if('config' == Object.keys(json)[0]) {
/*
* send config data where needed - future proofed, just add cases
* and namespaced objects where required
*/
kioskname['name'] = json.config['kiosk-name'];
}
}
});
// attach objects to namespace
window.KIOSK.kioskname = kioskname;
Now we can use the object to set local storage. In our case we post a message to the app's server and it responds with localStorage.setItem():
Post the message:
var currentFrame = document.getElementsByTagName('iframe')[0].contentWindow;
currentFrame.postMessage(JSON.stringify({key: 'user-name', data: KIOSK.kioskname.name}), "*");
By opening a socket and using the JSON string passed through the socket to populate a namespaced object we are able to use server-side information from a configuration file in our application's client.
I need to parse json object when the node server.js(which is my entry point to the program) is started ,the parse of the json file is done in diffrent module in my project.
I've two questions
Is it recommended to invoke the parse function with event in the server.js file
I read about the event.emiter but not sure how to invoke function
from different module...example will be very helpful
I've multiple JSON files
UPDATE to make it more clear
if I read 3 json file object (50 lines each) when the server/app is loaded (server.js file) this will be fast I guess. my scenario is that the list of the valid path's for the express call is in this json files
app.get('/run1', function (req, res) {
res.send('Hello World!');
});
So run1 should be defined in the json file(like white list of path's) if user put run2 which I not defined I need to provide error so I think that when the server is up to do this call and keep this obj with all config valid path and when user make a call just get this object which alreay parsed (when the server loaded ) and verify if its OK, I think its better approach instead doing this on call
UPDATE 2
I'll try explain more simple.
Lets assume that you have white list of path which you should listen,
like run1
app.get('/run1', function
Those path list are defined in jsons files inside your project under specific folder,before every call to your application via express you should verify that this path that was requested is in the path list of json. this is given. now how to do it.
Currently I've develop module which seek the json files in this and find if specific path is exist there.
Now I think that right solution is that when the node application is started to invoke this functionality and keep the list of valid paths in some object which I can access very easy during the user call and check if path there.
my question is how to provide some event to the validator module when the node app(Server.js) is up to provide this object.
If it's a part of your application initialization, then you could read and parse this JSON file synchronously, using either fs.readFileSync and JSON.parse, or require:
var config = require('path/to/my/config.json');
Just make sure that the module handling this JSON loading is required in your application root before app.listen call.
In this case JSON data will be loaded and parsed by the time you server will start, and there will be no need to trouble yourself with callbacks or event emitters.
I can't see any benefits of loading your initial config asynchronously for two reasons:
The bottleneck of JSON parsing is the parser itself, but since it's synchronous, you won't gain anything here. So, the only part you'll be able to optimize is interactions with your file system (i.e. reading data from disk).
Your application won't be able to work properly until this data will be loaded.
Update
If for some reason you can't make your initialization synchronous, you could delay starting your application until initialization is done.
The easiest solution here is to move app.listen part inside of initialization callback:
// initialization.js
var glob = require('glob')
var path = require('path')
module.exports = function initialization (done) {
var data = {}
glob('./config/*.json', function (err, files) {
if (err) throw err
files.forEach(function (file) {
var filename = path.basename(file)
data[filename] = require(file)
})
done(data);
})
}
// server.js
var initialization = require('./initialization')
var app = require('express')()
initialization(function (data) {
app.use(require('./my-middleware')(data))
app.listen(8000)
})
An alternative solution is to use simple event emitter to signal that your data is ready:
// config.js
var glob = require('glob')
var path = require('path')
var events = require('events')
var obj = new events.EventEmitter()
obj.data = {}
glob('./config/*.json', function (err, files) {
if (err) throw err
files.forEach(function (file) {
var filename = path.basename(file)
obj.data[filename] = require(file)
})
obj.emit('ready')
})
module.exports = obj
// server.js
var config = require('./config')
var app = require('express')()
app.use(require('./my-middleware'))
config.on('ready', function () {
app.listen(8000)
})
I am trying to get the file sizes of all my images/css/js from my website. How can you do this in nodejs?
What I recommend is performing this operation after the page has loaded, and using a token-authorized client to determine the size of the page document, the resources it is using (both CSS and JavaScript) and the markup size. On the server side, you'll receive an API request with all the files you need to stat. Here's an example of what this could look like on the server side assuming the use of Express.js:
express.all('/api/pageSize',function(req,res,next){
var fs = require("fs"); //Load the filesystem module
var totalSize = 0;
var pageURL = req.query.pageURL;
var files = req.query.files;
for ( var i in files ) {
var stats = fs.statSync(files[i])
total += stats["size"]
}
// do something with total size for this page
}