Send file from Node using Express within a POST route - node.js

I need to send the client (browser) a file after the client invokes a POST endpoint within my API. I tried using res.download(filename) but while there's no error, it doesn't do anything, e.g., pop up a download dialog, open in default viewer, etc.
function onArchiveCompleted (res, zip) {
const archive = `${uuidv4()}.zip``zip.writeZip(archive)
res.download(archive)
}
The "res.download()" returns 200 but no file dialog is ever presented on the client. However, if you look in the Network panel (browser developer tools) you can see that the file contents were returned.
What am I doing wrong?
TIA

Related

node.js - download files and content from DB to client

I want to send to the client in the same request files from the dir and some content from the DB.
DB query -
const derivatives = await Derivative.findAll();
Here is the res -
res.status(200).send({
data: derivatives.map((derivative) => ({
id: derivative.id,
date: derivative.date,
})),
});
Here is the download -
const fileName = derivatives.map((name) => name.wex);
res.status(200).download(__dirname, `/assets/${fileName}`);
How can I add that to my response?
HTTP lets you specify a "content disposition" to indicate whether a response should be treated as a download, but doesn't support sending downloads arbitrarily, it has to be a response to a request. You can't have part of a response be a download and part be not, for a single request.
So if you need a file to be downloaded, and some JSON used to display some UI, you need to handle that in the client somehow. Either:
The client sends a request, and server returns JSON containing a URL for the download as well as the other data you wanted to send, and then the client requests a download of the URL through JavaScript
The client sends two requests, one for the download and one for the other data; this may complicate things on the server if you need to associate the two requests (want to do a database lookup only once for instance), but is simplest on the client.
The client sends a request, and the server returns a response containing the JSON data and the file data, packed in some way (the file data could be inside the JSON but that would be inefficient), and it's unpacked on the client (using JavaScript) and the client then constructs a Blob URL to "download" (in this case the data is already downloaded, so this just entails saving a file)
There are any number of ways you might pack the file and JSON data together, which is what /u/Quentin was alluding to. Sending both as one response may be better for performance, but you probably don't need to.

running function after res.send

I'm trying to run this code
module.exports = async (req, res, next) => {
res.set('Content-Type', 'text/javascript');
const response = {};
res.status(200).render('/default.js', { response });
await fn(response);
};
fn is a function that calls an api to a service that will output to the client something. but its dependent on the default.js file to be loaded first. How can do something like
res.render('/default.js', { response }).then(async() => {
await fn(response);
};
tried it, but doesn't seem to like the then()
also, fn doesn't return data to the client, it calls an api service that is connected with the web sockets opened by the code from default.js that is rendered.
do i have to make an ajax request for the fn call and not call it internally?
any ideas?
Once you call res.render(), you can send no more data to the client, the http response has been sent and the http connection is done - you can't send any more to it. So, it does you no good to try to add something more to the response after you call res.render().
It sounds like you're trying to put some data INTO the script that you send to the browser. Your choices for that are to either:
Get the data you need to with let data = await fn() before you call res.render() and then pass that to res.render() so your template engine can put that data into the script file that you send the server (before you send it).
You will need to change the script file template to be able to do this so it has appropriate directives to insert data into the script file and you will have to be very careful to format the data as Javascript data structures.
Have a script in the page make an ajax call to get the desired data and then do your task in client-side Javascript after the page is already up and running.
It looks like it might be helpful for you to understand the exact sequence of things between browser and server.
Browser is displaying some web page.
User clicks on a link to a new web page.
Browser requests new web page from the server for a particular URL.
Server delivers HTML page for that URL.
Browser parses that HTML page and discovers some other resources required to render the page (script files, CSS files, images, fonts, etc...)
Browser requests each of those other resources from the server
Server gets a request for each separate resource and returns each one of them to the browser.
Browser incorporates those resources into the HTML page it previously downloaded and parsed.
Any client side scripts it retrieved for that page are then run.
So, the code you show appears to be a route for one of script files (in step 5 above). This is where it fits into the overall scheme of loading a page. Once you've returned the script file to the client with res.render(), it has been sent and that request is done. The browser isn't connected to your server anymore for that resource so you can't send anything else on that same request.

On trying to download file from Node JS, In browser getting error as: Failed network error

I am trying to download a file located in my storage in box.com.
When user made a api request with specific id then i am getting file url related to that id from box.com and in node js again making another api call to get the file chunks and returning it to the client.
Process is:
User make an api call as: /api?id=123 to node js
In node js, I am getting file url related to id 124.
Obtained file url is: https://box.com/xyz_123
Using this file url, I am making again an api call to the box server and getting file as chunk by chunk and returning it to client.
When the file is about to finish in 5 seconds, then in browser it is showing error as: Failed network error.
Here is the way i am calling the box url to get file streams and returning to client side chunk by chunk.
const creq = https.request(boxurl, (cres) => {
cres.setEncoding('utf8');
res.writeHead(cres.statusCode,
{
'Content-Length': cres.headers['content-length'],
'Content-Type': 'application/octet-stream',
'Content-Disposition' : cres.headers['content-disposition']
}
);
// wait for data
cres.on('data', function(chunk){
res.write(chunk);
});
cres.on('close', function(){
// closed, let's end client request as well
return res.end('Success');
});
cres.on('end', function(){
// finished, let's finish client request as well
return res.end('Success');
});
}).on('error', function(e) {
// we got an error, return 500 error to client and log error
return res.end(e.message);
});
creq.end();
File download mechanism working very well in my system.
When i deployed code to remote server and checking, then only this error is appearing.
Can any one please help me on this. How to solve this.
Thanks in advance.
To make use of the Box Node SDK you have to create a Box app in the Box Developer Console. Then you need to configure and authorise that app. An example can be found here
You can now write your node app that uses the Box SDK to talk to your Box app / account. An example of how to connect it up can be found here.
The code to download a file from Box can be found from Box's SDK documentation here. But before you can download a file you will need to discover its File Id by using other SDK methods such as getting a folder's items or search for content.
The folder id of your root folder in Box is always 0 which is a good place to start when searching for various file or folder id numbers.

Node/Express: File not downloading using fs.pipe()

I'm having an issue downloading files to my local machine using fs.pipe() in my Node.js/Express cloud foundry application.
Basically, a POST request is sent to my server side code containing the file name my user wants to download. I access the file using the GET command from the npm module ssh2-sftp-client. Finally, this file gets saved to the users local downloads folder using the npm module downloads-folder to identify this location. The code looks like this:
app.post('/download-file', function(req, res) {
// Declare the files remote and local path as a variable.
const remoteFilename = 'invoice/csv/' + req.body.file;
const localFilename = downloadsFolder() + '/' + req.body.file;
// Use the SFTP GET command to get the file passing its remote path variable.
sftp.get(remoteFilename).then((stream) => {
// Download the file to the users machine.
stream.pipe(fs.createWriteStream(localFilename));
// Redirect user.
res.redirect('/invoice')
});
})
This works perfectly when running locally and the file gets downloaded with no issues. As this screenshot shows, the output for the destination file path is this:
However, when I push this to our cloud foundry provider using cf push, the application still works fine but when I want to download the file it fails. I get no errors when error catching, the only thing thats changed is that the output for the destination file path has changed to:
I have no idea why this is, this code works fine in Chrome, Safari when running locally but when hosted doesn't do anything. Can anyone explain what's going wrong here?
Many thanks,
G
// Download the file to the users machine.
It doesn't do that, though: it downloads the file to the machine on which the server code is running. That's why it seems to work when you run the server on localhost, because the server machine and the user machine are the same.
Offering the file as a download involves streaming the file through the response object, making sure that you set the correct header. Something like this:
sftp.get(remoteFilename).then((stream) => {
res.set('content-disposition', `attachment; filename="${ req.body.file }"`);
stream.pipe(res);
});
When offering a file as "attachment", it typically opens the download window of the browser and the browser stays on the same page. You can't both stream and perform a redirect, but because the browser doesn't change the URL, that should be okay.
Also, you have no control over where the user will download the file to. It may be the downloads folder, but they are free to chose another folder.

how to check if my javascript browser extension works

I am trying to create a simple javascript based extension for Google Chrome that takes data from one specific iframe and sends it as part of a POST request to a webpage.
The web page that sends the data submitted by POST request, to my email address.
I tried running the extension, it looks to be running fine, but I am not getting any email.
The servlet which receives form data is very simple, I dont think there is any error in it.
What I want is some way to check if the javascript based extension works or not.
The javascript code is given below-
var mypostrequest=new ajaxRequest()
mypostrequest.onreadystatechange=function(){
if (mypostrequest.readyState==4){
if (mypostrequest.status==200 || window.location.href.indexOf("http")==-1){
document.getElementById("result").innerHTML=mypostrequest.responseText
}
else{
alert("An error has occured making the request")
}
}
}
var namevalue=encodeURIComponent("Arvind")
var descvalue=encodeURIComponent(window.frames['test_iframe'].document.body.innerHTML)
var emailvalue=encodeURIComponent("arvindikchari#yahoo.com")
var parameters="name="+namevalue+"&description="+descvalue &email="+emailvalue
mypostrequest.open("POST", "http://taurusarticlesubmitter.appspot.com/sampleform", true)
mypostrequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
mypostrequest.send(parameters)
UPDATE
I made changes so that the content in js file is invoked by background page, but even now the extension is not working.
I put the following code in background.html:
<script>
// Called when the user clicks on the browser action.
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript( null, {file: "content.js"});
});
chrome.browserAction.setBadgeBackgroundColor({color:[0, 200, 0, 100]});
</script>
Looking at your code looks like you are trying to send cross domain ajax request from a content script. This is not allowed, you can do that only from background pages and after corresponding domains are declared in the manifest. More info here.
To check if your extension works, you can open dev tools and check if there any errors in the console. Open "Network" tab and see if request was sent to your URL. Place console.log in various places in your code for debugging, or use full featured built in javascript debugger for step-by-step debugging.

Resources