Sending Zip file from server to client browser with Express and Archiver - node.js

I am a beginner with Node and I am trying to figure out how to create a zip file at the server then send it to the client and then download the zip file to the user's browser. I am using the Express framework and I am using Archiver to actually do the zipping. My server code is the following which was taken from Dynamically create and stream zip to client
router.get('/image-dl', function (req,res){
res.writeHead(200, {
'Content-Type': 'application/zip',
'Content-disposition': 'attachment; filename=myFile.zip'
});
var zip = archiver('zip');
// Send the file to the page output.
zip.pipe(res);
// Create zip with some files. Two dynamic, one static. Put #2 in a sub folder.
zip.append('Some text to go in file 1.', { name: '1.txt' })
.append('Some text to go in file 2. I go in a folder!', { name: 'somefolder/2.txt' })
.finalize();
});
So its zipping two text files and returning the result. On the client side I am using the following function in a service to actually call that endpoint
downloadZip(){
const headers = new Headers({'Content-Type': 'application/json'});
const token = localStorage.getItem('token')
? '?token=' + localStorage.getItem('token')
: '';
return this.http.get(this.endPoint + '/job/image-dl' + token, {headers: headers})
.map((response: Response) => {
const result = response;
return result;
})
.catch((error: Response) => {
this.errorService.handleError(error.json());
return Observable.throw(error.json());
});
}
and then I have another function which calls downloadZip() and actually downloads the zip file to the user's local browser.
testfunc(){
this.jobService.downloadZip().subscribe(
(blah:any)=>{
var blob = new Blob([blah], {type: "application/zip"});
FileSaver.saveAs(blob, "helloworld.zip");
}
);
}
When testfunc() is called, a zip file is downloaded to the user's browser however when I try to unzip it it creates a zip.cpgz file which then turns back into a zip file when clicked in an infinite loop which indicates that some kind of corruption happened. Can anyone see where I went wrong here?

Related

Mysql data to Excel File

I am working on nodeJs and React, I have data in mysql storage.
ultimately i need to let the user to download the data in excel format.
Either we can do in nodeJs or React.
I tried to create a file in Node using excel4node package, The file gets created successfully, but when i send the file, it is not in excel format(some xml files and folders), i used downloadJs in frontend to trigger autoDownload.
router.get('/:year/:month', async (req, res, next) => {
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition', 'attachment; filename=' + 'Report.xlsx');
res.sendFile(path.resolve('downloads/excel.xlsx'));
});
import downloadjs from 'downloadjs';
export const getReport = async (year, month) => {
let res = await fetch(`${url}/get-report/${year}/${month}`, {
method: 'GET',
mode: 'cors',
})
let blob = await res.blob();
await downloadjs(blob);
};
This downloads a zip folder which has list of xml files.
I tried to create in React (client side) by sending json from the backend,
for this i used react-excel-workbook package, but it needs a predefined data, when we click, it suddenly gets downloaded with dummy data and it doesn't wait for async action to resolve.
Any help will be appreciated.
Or should i send the json from backend and on client side (convert it into csv and trigger download.??
Write the file directly to the Response object, instead of going through an intermediate file
var xl = require('excel4node');
var wb = new xl.Workbook();
// sends Excel file to web client requesting the / route
// server will respond with 500 error if excel workbook cannot be generated
var express = require('express');
var app = express();
app.get('/', function(req, res) {
wb.write('ExcelFile.xlsx', res);
});
app.listen(3000, function() {
console.log('Example app listening on port 3000!');
});
Late answer but you should specify content type when you are creating the blob in your frontend, then create a link in your DOM and specify to browser that the file must be downloaded :
axios.get(`${your backend url goes here}/path/to/export`, {
responseType: 'blob',
headers: {
'Authorization': `Bearer ${token}` //Or any auth method
}
}).then(res => {
const url = window.URL.createObjectURL(new Blob([res.data]), {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}); //specify CT
const link = document.createElement('a'); // attach link to DOM
link.href = url;
link.setAttribute('download', 'File.xlsx');
document.body.appendChild(link);
link.click(); // Auto dl the file
link.remove(); // Remove the link
}).catch(err => {
console.log(err);
})

Firebase Cloud Function Serving Local File for Download

I created cloud function that generates an xlsx file, I need the user to download that file after it's generated.
Method 1: Upload to Bucket, then redirect
So far i've tried uploading the file to a bucket using this API, and then redirect him to the bucket file url, I also double checked the bucket name using this API, but I get the same error every time:
{"error":{"code":500,"status":"INTERNAL","message":"function crashed","errors":["socket hang up"]}}
Portion of the code that contains uploading to bucket:
const { Storage } = require('#google-cloud/storage');
const storage = new Storage();
await storage.bucket('bucket-name').upload('myfile.xlsx', {
gzip: false,
});
Portion of the code that proves file exists:
fs.access('myfile.xlsx', fs.constants.F_OK, (err) => {
console.log(`${file} ${err ? 'does not exist' : 'exists'}`);
});
I also checked if the library "#google-cloud/storage" reads the file, and it reads it correctly and gets the file size right.
Method 2: Direct Download
Download the file directly, the problem is that every doc online for nodejs to download a local file to the user is setting up a custom server to download the file, but i'm using firebase, so it's not in control of that server.
Just wanted to add more detail to the answer, since there's no need to write into a file and read from it to download it's data, simply take the data and send it, using the few lines below.
res.setHeader('Content-Type', 'application/vnd.openxmlformats');
res.setHeader("Content-Disposition", "attachment; filename=" + fileName);
res.end(fileData, 'binary');
If your excel file is created and should be returned to the client as a response to an HTTP request (calling to an API endpoint) then this is how you can do it.
export const getExcelFile = functions.https.onRequest(async (request, response) => {
// ...
// Create your file and such
// ..
await storage.bucket('bucket-name').upload('myfile.xlsx', {
gzip: false,
});
response.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
response.send(fs.readFileSync('myfile.xlsx'));
return null;
});
Otherwise, if the excel file is created as a response to an event, and you want the user to download the file at another time, then you create a download link and serve it to the user in any way you want.
// ...
// Create your file and such
// ..
const [file] = await storage.bucket('bucket-name').upload('myfile.xlsx', {
gzip: false,
});
const [downloadUrl] = await file.getSignedUrl({
action: 'read',
expires: '20-03-2019' // Link expiry date: DD-MM-YYYY
});
console.log(downloadUrl);

Display PDF file in ReactJS that is received from a Node.js server?

I am trying to build a system where a user can store pdf files on a server, and another user can view those pdf files by using a simple click on a file link.
I am trying to store a file in a MySQL database and retrieve it using app.get(). I have successfully stored the file in the database using BLOB, but when I try to retrieve it, it is in some other format.
I have also tried to store the file in local folder ./uploads using 'express-fileupload', but that also doesn't seem to work when I try to retrieve the file location. After receiving the file location I am sending it back to my React app, and then try to open it using embed and iframe tags.
I have also tried 'react-pdf', 'simple-react-pdf', but nothing seems to work.
Below is the code that is written on server side that is sending the pdf file. I have also tried sending the location of pdf file that is stored in location provided in the code below. But that also doesn't work.
app.get('/getFile', (req, res) => {
const {email, courseid, filename} = req.query;
console.log(email);
console.log(courseid);
console.log(filename);
var filePath = `${__dirname}`+'/uploads/'+`${filename}`;
fs.readFile(filePath , function (err,data){
console.log(data);
res.contentType("application/pdf");
res.send(data);
});
});
This worked for me:
Node:
app.get("/getFile", function(req, res) {
res.sendFile(__dirname + "/test.pdf");
});
React:
axios(`http://localhost:5000/getFile `, {
method: "GET",
responseType: "blob"
//Force to receive data in a Blob Format
})
.then(response => {
//Create a Blob from the PDF Stream
const file = new Blob([response.data], {
type: "application/pdf"
});
//Build a URL from the file
const fileURL = URL.createObjectURL(file);
//Open the URL on new Window
window.open(fileURL);
})
.catch(error => {
console.log(error);
});

Node - sequential download and zip extraction

I am creating an Express server which would allow the client to receive downloaded files from different sources.
One of the file that is downloaded is a zip file, which needs to be extracted before it is send to the client. The API which is exposed to the node client has a Promise.all() to ensure that files are not send back until everything is downloaded and extracted.
// Client would call this as /download API. Once the download is completed (along with zip extraction), these files would be send to client
function download() {
return Promise.all([downloadInvoice(), downloadImages()]);
})
}).then(function (result) {
// this is happening even before the zip is extracted
console.log('content downloaded and zip extracted, send everything to client');
})
})
}
The downloadImages function calls a REST service (POST) to get images, which are then written to filesystem using pipe. Once the zip is saved in the file system, I am using extract module to extract the zip to a folder.
var req = require('request');
var extract = require('extract-zip')
function downloadImages() {
return new Promise(function(resolve, reject) {
var imageUrl = 'http://localhost:8080/restserver/downloadImages';
var postData = {
username: 'abc',
password: 'xyz'
}
var options = {
method: 'post',
body: postData,
json: true,
url: imageUrl
}
req(options, function (err, res, body) {
if (err) {
console.log('Error posting json '+err)
return;
}
console.log('Status code '+res.statusCode);
}).pipe(fs.createWriteStream('C:/temp/Images.zip'))
.on('finish',function() { // 'finish' fired as download completed. Now extract
console.log('Finished downloading Images, now extract them');
extract('C:/temp/Images.zip',
{dir: 'C:/temp/Images'},
function (err) {
if(err) {
console.log('Error extracting Images zip '+err);
}
resolve("Promised resolved for Images");
})
}).on('error', function(error) {
reject('Error extracting images', error);
});
}
}
Challenge that I am facing is that, even before zip extracts, Promise.all resolves. So, an empty folder is send to the client.
I would like things to happen in this order -
1. Promise.all in download() is used to instantiate the download
2. DownloadImages() downloads the zip of images and saves on the node server (using pipe)
3. DownloadImages() extracts the zip
4. Promise.all in download() is completed and extracted images are send to the client.
Step 3 is happening after step 4. I am not able to make out what am I doing incorrectly to chain these operations in the right sequence.

Nodejs Express Send File

I am trying to send a file's content to the client in my request, but the only documentation Express has is it's download function which requires a physical file; the file I am trying to send comes from S3, so all I have is the filename and content.
How do I go about sending the content of the file and appropriate headers for content type and filename, along with the file's content?
For example:
files.find({_id: id}, function(e, o) {
client.getObject({Bucket: config.bucket, Key: o.key}, function(error, data) {
res.send(data.Body);
});
});
The type of file depends on the file obviously. Have a look at this:
http://en.wikipedia.org/wiki/Internet_media_type
If you know what exactly is your file, then assign one of these to response ( not mandatory though ). You should also add the length of the file to response ( if it is possible, i.e. if it is not a stream ). And if you want it to be downloadable as an attachment, then add Content-Disposition header. So all in all you only need to add this:
var filename = "myfile.txt";
res.set({
"Content-Disposition": 'attachment; filename="'+filename+'"',
"Content-Type": "text/plain",
"Content-Length": data.Body.length
});
NOTE: I'm using Express 3.x.
EDIT: Actually Express is smart enough to count content length for you, so you don't have to add Content-Length header.
This is a great situation to use streams. Use the knox library to simplify things. Knox should take care of setting the needed headers to pipe files to the client
var inspect = require('eyespect').inspector();
var knox = require('knox');
var client = knox.createClient({
key: 's3KeyHere'
, secret: 's3SecretHere'
, bucket: 's3BucketHer'
});
/**
* #param {Stream} response is the response handler provided by Express
**/
function downloadFile(request, response) {
var filePath = 's3/file/path/here';
client.getFile(filePath, function(err, s3Response) {
s3Response.pipe(response);
s3Response.on('error', function(err){
inspect(err, 'error downloading file from s3');
});
s3Response.on('progress', function(data){
inspect(data, 's3 download progress');
});
s3Response.on('end', function(){
inspect(filePath, 'piped file to remote client successfully at s3 path');
});
});
}
npm install knox eyespect

Resources