Express.js: respond with image file instead of binary file? - javascript

In express, I'm trying to respond with an image to a React request, however, I get the image binary file instead of the image file.
I tried using res.sendFile and res.download, but they send the binary file. I also tried res.attachment but it works inconsistently, and somehow makes the respond pending.
I tried specifying the file type with res.type("image/jpeg"), but it doesn't make a difference.
router.get("/photo", (req, res) => {
res.type("image/jpeg");
res.sendFile("/absolute/path/image.jpg");
});
I'm expecting an image file with normal properties of name, size, etc.

I think the distinction you are drawing between an "image file" and an "image binary file" is misleading you. All bitmap image files are encoded in binary (or, on rare occasions, base64), so I don't think that gets to the root of your problem.
res.sendFile should work just fine. The problem is in your client-side JavaScript.
To display image data in the browser, you will ultimately have to use canvas or img HTML elements. The easiest way to asynchronously load an image in your app would be to update the src attribute of an img element that already exists in the DOM to the address of the image and let the browser handle the loading for you.
However, if you want to manipulate the image data prior to loading it to an img or canvas element, I would recommend using the FileReader API, as opposed to manually parsing the binary.
The key step with this approach is to set the response data type to "blob" when you make your get request
The blob data type references the binary image file, but allows you to use the browser's built-in File interface.
The code below requests an image file as a blob and then converts the blob into a base64 encoded data url that you can use as the src attribute of an img element or load to a canvas.
var xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.onload = function(event) {
fileToDataUrl(event.target.response, function(result){
console.log(result);
});
};
xhr.open('GET', "https://i.imgur.com/7VhSUEH.jpg", true);
xhr.send();
function fileToDataUrl(fileObj, callback) {
var reader = new FileReader();
reader.addEventListener("load", function() {
console.log("result")
callback(reader.result);
}, false);
reader.readAsDataURL(fileObj);
}

Try this:
const fs = require("fs");
router.get("/photo", (req, res) => {
fs.readFile("/absolute/path/image.jpg", function(err, data) {
if (err) return next(err);
res.set("Content-Type", "image/jpeg");
return res.status(200).end(data, 'binary');;
});
});

var filename = __dirname+ imagePath;
var readStream = fs.createReadStream(filename);
readStream.on('open', function () {
readStream.pipe(res);
});
readStream.on('error', function(err) {
res.end(err);
});
Please don't forget to put fs dependency

Related

How to convert between MIME-Types for blob/buffer?

The Problem:
I record Audio from within the Browser which gives me a BLOB when the recording is done:
let blob = new Blob(chunks, { 'type' : 'audio/webm;codecs=opus' });
Changing the mime-type here won't help since the chunks already come with their MIME-Type which is audio/webm;codecs=opus for almost all browsers. So can not do anything here.
Sending this Blob via XHR to an node.js server will result in recieving a buffer from that blob:
Client:
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost:3000/audio', true);
xhr.send(blob);
Server:
app.post('/audio' , (req, res) =>{
req.on('readable',()=>{
let buffer = req.read();
// sending this buffer to the external API results in error
// since it expects the mime-type audio/wav
});
res.send({msg: 'success'});
});
Most solutions out there require you to write the file to your disk and convert that ( ffmpeg).
Others use Browser-features which are experimental or not compatible with older browsers...
I also tried using the wavfile npm package, but that creates a corrupted file if i try writing it with the UInt8Array from that webm-formatted buffer (playable file but it only contains noise and is much shorter than the actual recording should be)
There must be a simple solution to convert the binary data server side, right? Best I could wish for would be a function convertWebmBufferToWavBuffer.

socket.io, node.js forwarding image from server to client

I want to receive an image via socket.io on node.js and would like forward it to a client (browser), but the image sent via the message to the browser is not recognised and therefore not show.
However, when I save the message/image first on the node.js server and load the saved file again to forward the image it works fine. I can also open the jpeg file on the server from the file system without a problem. Sending a different jpeg directly from the server works also as expected.
socket.on('image', function(msg) {
var fileName = 'clientImage.jpg';
// First save the file
fs.writeFile(fileName, msg.buffer, function() {});
// reload the image and forward it to the client
fs.readFile(__dirname +'/clientImage.jpg', function(err, buf){
socket.emit('serverImage', {image: true, buffer: buf});
});
}
If I simplify the function to forward the message (msg) received without the "fs" workaround, like:
socket.emit('serverImage', {image: true, buffer: msg.buffer});
or in the simples expected way
socket.emit('serverImage', msg);
the message will not be recognised as an image on the browser and the client does not fire the "onload" event for the Image.
Client code (works with jpeg files fine):
socket.on('serverImage', function(msg) {
var blob = new Blob([msg.buffer], {type: 'image/jpeg'} );
var url = URL.createObjectURL(blob);
var limg = new Image();
limg.onload = function () {
console.log(' -- image on load!');
rcontext.drawImage(this, 0, 0);
URL.revokeObjectURL(url);
};
limg.src = url;
});
Is there a way that the message can be adopted/converted somehow i.e. encoding, to be recognised directly without the "fs" library, or any other suggestions?
many thanks!
Many thanks for the responses,
I did further tests and found a workaround / solution by using an additional buffer variable specifying the type in front of the socket.emit :
var nbuffer = new Buffer(msg.buffer,'image/jpeg');
socket.emit('serverImage', {image: true, buffer: nbuffer});
with this additional step, the browser recognises now the message as image.
Many thanks for your help!
writeFile is asynchronous. It takes time to write a file to the disk. You passed it a callback function, but it's empty. Re-read the image inside that callback function.
// First save the file
fs.writeFile(fileName, msg.buffer
, function() { // When writing is done (that's the important part)
// reload the image and forward it to the client
fs.readFile(__dirname +'/clientImage.jpg', function(err, buf){
socket.emit('serverImage', {image: true, buffer: buf});
});
});

Decode image from base64 to jpg

I am capturing an image on one client and sending the image to another client via socket.io to be sent out to users as a jpg. On the client capturing the image I am doing :
fs.readFile('./app/image.jpg', function(err, buf) {
socket.emit('image', { image: true, buffer: buf.toString('base64') });
})
This part is working fine and is encoding the image and emiting it. On the other client I have :
socket.on('image', function(img) {
console.log(img);
});
This client is receiving the message and can log out the encoded image.
I am struggling converting the image from base64 to a jpg again. What do I need to do in order to accomplish this?
Something like this:
socket.on('image', function(img) {
var buffer = new Buffer(img, 'base64');
// Now you probably want to save it as a file...
});

"Echoing" an image in Node.js

I have a fully functioning PHP application that I am trying to make a Node.js version of. It deals with serving image tiles. When it's ready to display the image it does:
// Stream out the image
echo self::$tile;
How would I do something similar in Node.js? I understand this is a broad question, but I think my biggest issue is that I don't understand how PHP "echoes" an image.
Details:
I'm using AWS to get the image. The AWS call returns a Buffer. At this point of time, in the Javascript I have left the image as a Buffer.
The site populates a map with tiled images, so there are multiple calls with the image placed at a particular location on the page. I am using express to handle the requests.
app.get(/^\/omb\/1.0.0\/(.+)\/(.+)\/(.+)\/(.+)\.[a-zA-Z]*$/, function(req, res){
var MosaicStreamer = require('./models/MosaicStreamer.js');
var ms = new MosaicStreamer;
var configs = {library: req.params[0], zoom: req.params[1], column: req.params[2], row: req.params[3]};
ms.handleTile(configs);
});
handleTile grabs the image and ultimately brings me to where I am now. The image is grabbed using the following:
var aws = new AWS.S3();
var params = {
Bucket: this.bucket,
Key: this.tileDirectory + this.filepath,
Range: 'bytes=' + (this.toffset + 4) + "-" + (this.tsize + this.toffset + 4)
};
var ts = this;
aws.getObject(params, function(err, data){
if(ts.tile == null){
ts.tile = data.Body; //S3 get object
}
}
I think what you want to do is take a given URL which represents closely the naming convention of folders/files in your S3 Bucket. So assuming that you've established a client connection to your S3, you can use the readFile method. The 2nd argument is an imageStream which you can pass in the response. Once the stream has ended from S3, it will automatically end the res from the client, outputting the image directly to the client (as you intend).
Some psuedo code:
app.get(/^\/omb\/1.0.0\/(.+)\/(.+)\/(.+)\/(.+)\.[a-zA-Z]*$/, function(req, res){
var MosaicStreamer = require('./models/MosaicStreamer.js');
var ms = new MosaicStreamer;
var configs = {library: req.params[0], zoom: req.params[1], column: req.params[2], row: req.params[3]};
return ms.handleTile(configs, res);
//return the handleTile function, add 2nd argument and pass res through
});
Inside of handleTile function you can make the call for the S3
function handleTile(configs, res){
client.readFile('filename', function(error, imageStream){
imageStream.pipe(res);
});
}
Now requests to images like this:
<img src="/path/to/my/file/that/matches/regexp/expression"/>
It will request that image from the S3 Bucket and stream the resource back to the client directly.
To successfully render an image, you have to implement three steps:
Retrieve the image data (for instance as a Buffer read via fs.readFile) or a stream (for instance via fs.createReadStream
Set the appropriate headers in the web request handler with the arguments (req, res); something like
res.writeHead(200, {'Content-Type': 'image/png'});
Write the file. If you have the file in a Buffer, with
res.end(buf, 'binary');
If you have a stream via
read_stream.pipe(res)
The whole code may look like (assuming you want to serve the file image.jpg from the current directory):
'use strict';
var fs = require('fs');
var http = require('http');
http.createServer(function(req, res) {
fs.readFile('image.jpg', function(err, buf) {
if (err) {
res.writeHead(500);
res.end('Cannot access file.');
return;
}
res.writeHead(200, {'Content-Type': 'image/jpeg'});
res.end(buf, 'binary');
});
}).listen(8002, '');
Using a stream, a very simple version (beware: no error handling, with error handling it can get a little bit more complex, depending how you want to handle errors occurring while the file is being read)
'use strict';
var fs = require('fs');
var http = require('http');
http.createServer(function(req, res) {
var stream = fs.createReadStream('image.jpg');
// Error handling omitted here
res.writeHead(200, {'Content-Type': 'image/jpeg'});
stream.pipe(res);
}).listen(8003, '');
Code that uses a Buffer is easier to write, but means that your server must hold the whole file in memory - for instance, you will be unable to serve a 320 Gigapixel image file. You also only start sending data once you have the whole file.
Using a stream allows sending the file as soon as you get it, so it will be a little faster. If you're reading from file or a local fast server the speed difference is likely negligible. In addition, you'll only need a little bit of memory. On the other hand, error handling is more complex.

Saving images from URL using JSzip

I'm using JSzip to download the html of a div. The div has images inside of it (they're not base64 encoded). Is there a way I can use JSzip to download the files from their image path url? or do they have to be base64 encoded?
My current code is just the basic demo code from the JSzip site (http://stuk.github.io/jszip/)
var zip = new JSZip();
var email = $('.Result').html();
zip.file("test.html", email);
var content = zip.generate({type:"blob"});
// see FileSaver.js
saveAs(content, "example.zip");
You might want to try using JSzip-utils it has a call just for downloading images from urls also take a look at the example in JSzip documentation I found it to be very good. You can find working example with code here.
This is just part for downloading that I'm also using to download images from social media using their image source urls.
function urlToPromise(url) {
return new Promise(function(resolve, reject) {
JSZipUtils.getBinaryContent(url, function (err, data) {
if(err) {
reject(err);
} else {
resolve(data);
}
});
});
}
var zip = new JSZip();
zip.file(filename, urlToPromise(url), {binary:true});
zip.generateAsync({type:"blob"})
.then(function callback(blob) {
// see FileSaver.js
saveAs(blob, "example.zip");
});
Here is my solution (adapted from here) building within an angular framework (though readily applicable to other frontend approaches):
NOTE: this only works if you are packaging resources -- EVEN IMAGES -- from the same origin, or that are served with 'cross-origin-resource-sharing': '*'
Make sure the JSZip UMD is included in your global namespace. In my angular case, I saved it via npm i -S jszip, and then copied the node_modules/jszip/dist/jszip.js script to my src/assets folder and included it in angular.json's scripts array.
Angular only: to get the jszip typescript definition file to work, copy node_modules/jszip/index.d.ts somewhere into src
Download npm i -S file-saver and import it as an ES6 module (see below).
Run the following function when you want the download event to occur:
import { saveAs } from 'file-saver';
async downloadData() {
// Fetch the image and parse the response stream as a blob
const imageBlob = await fetch('[YOUR CORS IMAGE URL]').then(response => response.blob());
// create a new file from the blob object
const imgData = new File([imageBlob], 'filename.jpg');
// Copy-pasted from JSZip documentation
var zip = new JSZip();
zip.file('Hello.txt', 'Hello World\n');
var img = zip.folder('images');
img.file('smile.gif', imgData, { base64: true });
zip.generateAsync({ type: 'blob' }).then(function(content) {
saveAs(content, 'example.zip');
});
}
First of all you need to download all the images with ajax. if they are on the same domain you are in luck, otherwise you need CORS or a proxy.
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function(){
if (xhr.status == 200){
//Do something with xhr.response (not responseText), which should be a Blob
}
});
xhr.open('GET', 'http://target.url');
xhr.responseType = 'blob';
xhr.send(null);
When you got the image you have to manipulate the src in all <img>'s either you replace them with base64 or referring them to a folder were you have put them in a folder with JSZip
var reader = new FileReader();
reader.onload = function() {
showout.value = this.result;
};
reader.readAsDataURL(xhr.response);

Categories

Resources