Why express app.render loads images with local file path synchronously? - NodeJS - node.js

I noticed the following:
When I want to render an array of image URLs hosted in another website like this:
index.js
MongoClient.connect('mongodb://localhost:27017/sample', function(err, db) {
db.collection('collSample').findOne( { somevar: 'somevalue' }, function(err, doc) {
assert.equal(null, err);
res.render('index', {
title: 'render images' ,
imagesPaths: doc.images.`**URLs**`
});
db.close();
});
});
index.jade
- each path in imagesPaths
span
img(src="#{path}")
I get this Network info from Chrome Developer Tools. Notice how the images are being rendered asynchronously
But, if I use images stored localy like this:
index.js
MongoClient.connect('mongodb://localhost:27017/sample', function(err, db) {
db.collection('collSample').findOne( { somevar: 'somevalue' }, function(err, doc) {
assert.equal(null, err);
res.render('index', {
title: 'render images' ,
imagesPaths: doc.images.`**localPath**`
});
db.close();
});
});
index.jade
- each path in imagesPaths
span
img(src="#{path}")
I get this Network info. Note that iamges are being rendered synchronously. Also, load time is considerably lower.
Question 1: How can I force rendering images asynchronously?
Question 2: Why sometimes nodejs renders images async and others sync? I thought that nodejs will render a bunch of images asynchronously, regardless of what is inside of image src attribute.

They are not loaded synchronously. All the requests are made async, (see that all the white rectangles begin to be printed at the same time).
The real difference stands in the path of the request towards the resource location:
every remote request get through the Internet and it is not guaranteed that the arrival order is the same at the start.
every local request is processed quite immediately, giving the illusion to be sync, quite preserving the order of the requests too.
Proof: Try add the local path of avery huge image o(N*10MB) or o(100MB) (for example http://www.nasa.gov/sites/default/files/thumbnails/image/hs-2015-02-a-hires_jpg.jpg ) in the middle of the array and see that the image is finishes to be load last.

Related

why am I getting favicon.ico when i am using findOne method for express params routes?

when i am using list.save() method a object other than customList name which is favicon.ico is also
saving as record in following cod, Why am i gatting favicon.ico as object.
app.get('/:listRoute',function (req,res) {
const customList=(req.params.listRoute);
List.findOne({name:customList }, function (err,result) {
if (!err) {
if (!result) {
const list=new List({
name: customList,
items: defaultItems
})
list.save();
} else {
console.log(result);
res.render('list', {
listTitle: result.name,
latestItems: result.items})
}
}
});
})
When you visit a website (any URL on that website), a browser will typically also send a request to that same domain for /favicon.ico so see if the web site offers an icon to be a visual representation of the site.
Since you are using a wildcarded top level route:
app.get('/:listRoute', ...)
That will get hit by the request for /favicon.ico. Some other urls you also may need to watch out for being requested are: /robots.txt, /humans.txt, /sitemap.xml, /ads.txt.
There are a number of ways to work around this:
Your wildcard route can first check req.url or req.params.listRoute to see if it's something it should ignore.
You can place other top level routes that you want to keep out of your wildcard route in a position before this route so they don't end up in this one.
Don't use a top level wildcard route. Instead, use something like /list/:listRoute so it won't automatically match any top level http request. Your use of a top level wildcarded route interferes with other future uses of your site and can create backwards compatibility going forward when you want to add other top level routes to your site. Imagine if sometime in the future, you want to add /contact or /login or /logout. Those all conflict with /:listRoute.
Try to add a callback function to the list.save();
Let me know if this works. The reason is maybe because of sync issues. eg: time taken by mongoDB to update the first document & save > the time taken by the 'Get' method to redirect to itself. Therefore by adding this callback it kinda make sure the code gets saved first and err checked before the redirect.
eg:
list.save(function(err){
if(!err) {
console.log("list is successfully saved"); //log is optional
res.redirect("/" + listRoute);
}
});
When fetching route data using params with express,the entered data easily we can log.But if not adding top-level route and just trying to get the required route eg:
app.get("/:requireddata",function(req,res){
const data = req.params.requireddata;
});
in this case, when loading main page the favicon.ico will generate as a result.
So for getting an exact result, that's when only loading requireddata route we can get the result by using higher level route.
In case there is no higher-level route add just an additional route before requireddata as shown below:
app.get("/add/:requireddata",function(){});
Here /add/ is an additional route for avoiding favicon.ico
For me this worked, so if this information is useful just go head.
Hey there I also came across this exact issue.
So here is my solution to that.
Just enclose everything in a if block and there you go. DONE !!!!
app.get("/:name", function (req, res) {
if (req.params.name != "favicon.ico") {
const name = _.capitalize(req.params.name);
List.findOne({ name: name }, (err, foundList) => {
if (!err) {
//new list with default items created
if (!foundList) {
const list = new List({
name: name,
items: defaultItems,
});
list.save();
res.redirect("/" + name);
} else {
res.render("list", {
listTitle: foundList.name,
newListItem: foundList.items,
});
}
}
});
}
});
P.s.:- It will throw some error from mongo but that'll not affect the overall working.
Hope this helps.

Use nodejs express to respond with existing image file that the browser can cache?

I have a small webapp built with nodejs and express (among other things) that has a route to resize images on the fly (using sharp). The route looks like this:
router.get('/image/:options/:basedir/:dir/:img', utilitiesController.getOptimizedImage);
In the utilities controller, I have the getOptimizedImage function checking for the existing image, returning the existing image content if it exists, or if it doesn't, performing some image processing tasks, then returning the resulting image.
exports.getOptimizedImage = async (req, res) => {
// Parse options from request...
// first, check to see if resized version exists
fs.readFile(processedImgPath, function (err, processedImg) {
if (err) {
//console.log('File does not yet exist.');
// If resized version doesn't exist, check for original
fs.readFile(originImgPath, function (err, originImg) {
if (err) {
// If origin image doesn't exist, return 400.
} else if (w || h) {
// If origin image does exist, process it...
// Once it's processed, return the processed image
res.end(newImg);
return;
}
}
} else {
//
res.end(processedImg);
//res.redirect(existingFileUrl);
return;
}
}
}
This code works. I can request something like so:
<img src="http://example.com/image/w800/foo/bar/imagename.jpg">
...and it returns the resized image as expected. The issue seems to be that because of the way the image is returned using res.end(), the browser cache (testing in Chrome) doesn't ever store the image, so reloading the page downloads the image fresh instead of loading it from memory or disk.
I can alternatively use res.redirect() to send back the url of the existing processed image file, which will be cached on refresh, but that feels like the wrong way to do this, since it ultimately doubles all the image requests using the image processing path.
I don't want to process the images prior to request; I'm specifically looking to only process the images the first time they're requested, then store a processed version to reference each consecutive time. I'm open to alternatives within these constraints, but hoping someone can explain how I might leverage browser caching with my current structure?
You should add http headers for caching before any res.end, the example below will set the Expires time to 1 day (86400000ms).
res.set({
"Cache-Control": "public, max-age=86400",
"Expires": new Date(Date.now() + 86400000).toUTCString()
})

Access uploaded image in Sails.js backend project

I am trying to do an upload and then accessing the image. The upload is going well, uploading the image to assets/images, but when I try to access the image from the browser like http://localhost:1337/images/image-name.jpg it gives me 404. I use Sails.js only for backend purposes - for API and the project is created with --no-front-end option. My front end is on AngularJS.
My upload function:
avatarUpload: function(req, res) {
req.file('avatar').upload({
// don't allow the total upload size to exceed ~10MB
maxBytes: 10000000,
dirname: '../../assets/images'
}, function whenDone(err, uploadedFiles) {
console.log(uploadedFiles);
if (err) {
return res.negotiate(err);
}
// If no files were uploaded, respond with an error.
if (uploadedFiles.length === 0) {
return res.badRequest('No file was uploaded');
}
// Save the "fd" and the url where the avatar for a user can be accessed
User
.update(req.userId, {
// Generate a unique URL where the avatar can be downloaded.
avatarUrl: require('util').format('%s/user/avatar/%s', sails.getBaseUrl(), req.userId),
// Grab the first file and use it's `fd` (file descriptor)
avatarFd: uploadedFiles[0].fd
})
.exec(function (err){
if (err) return res.negotiate(err);
return res.ok();
});
});
}
I see the image in the assets/images folder - something like this - 54cd1fc5-89e8-477d-84e4-dd5fd048abc0.jpg
http://localhost:1337/assets/images/54cd1fc5-89e8-477d-84e4-dd5fd048abc0.jpg - gives 404
http://localhost:1337/images/54cd1fc5-89e8-477d-84e4-dd5fd048abc0.jpg - gives 404
This happens because the resources your application accesses are not accessed directly from the assets directory but the .tmp directory in the project root.
The assets are copied to the .tmp directory when sails is lifted, so anything added after the lift isn't present in .tmp.
What I usually do is upload to .tmp and copy the file to assets on completion. This way assets isn't polluted in case the upload fails for any reason.
Let us know if this works. Good luck!
Update
Found a relevant link for this.

Publishing and subscribing to node-redis for an image resizing job after form POST

I've got a form submission that accepts an image. I'm creating thumbnails (resizing and pushing to S3) with the image upon submission, and it's taking awhile and blocking so I've decided I want to push it to a message queue and have that handle it.
I've decided to go with node-redis, since I'm already using redis in my stack. What I'm unclear on is how exactly the implementation would look (in its most basic form).
Consider some pseudocode below as:
var redis = require('redis'),
client = redis.createClient();
function listenForJob() {
client.on('message', function(msg) {
// msg is our temporary path name
// Kick of resize and push to s3 job
});
}
// Attached to my route where I POST to (e.g. /submit)
exports.form = function(req, res, next) {
// Input comes in, and image comes in as req.files
// and a temporary image is saved in /uploads
// which I'll save as the image submission for now
// until the process to resize and push to s3 happens.
listenForJob();
// Save to db
var data = {
title: req.body.title,
img: req.files.path // save temp file
}
connection.query('INSERT INTO submissions SET ?', data, function(err, rows) {
// Publish to our redis client?
client.publish('message', req.files.path);
connection.release();
res.redirect('/submissions');
});
};
Is this implementation even remotely the correct way to approach this? I'm new to taskworkers/message queues so I'm just wondering how to do implement it properly (given my use case).

PhantomJs - How to render a multi page PDF

I can create one-page PDFs with phantomJS; but I can't find on the doc how to create different pages (each page coming from an html view) and put them into one PDF ? I am using node-phantom module for NodeJS
Just need to specify a paperSize.
Like this with module "phantom": "0.5.1"
function(next) {
phantom.create(function(doc) {
next(null, doc);
}, "phantomjs", Math.floor(Math.random()*(65535-49152+1)+49152));
},
function(ph, next) {
ph.createPage(function(doc) {
next(null, doc);
});
},
function(page, next) {
page.set('paperSize', {format: 'A4', orientation: 'portrait'});
page.set('zoomFactor', 1);
}
Then, simply use page-break-before: always; in your HTML content each time you want to open a new page.
PS: I use async.waterfall in this example
PPS: Math.random on port number is used to avoid module crash if concurrent calls to phantom binary are triggered. Works fine - if someone wants to post something better even if a bit off-topic, feel free to do it

Resources