Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Need to get the filename from a file downloaded using javascript fetch api?

In my Javascript client, I'm using Fetch API to call the server to retrieve a server-generated file. I'm using the following client-side code:

var _url = "";    
var initParms = {  
   method: "GET",
   mode: 'cors'
}

fetch(_url, initParms)
.then(response => {
   if(response.ok){
      alert(response.headers.get("content-disposition"));
      return response.blob();
   }

   throw new Error("Network response was not OK.");
})
.then(blob => {
   var url = new URL.createObjectURL(blob);
})     

This actually works just fine. However, the server generates a filename for the file and includes it in the response as part of the content-disposition header.

I need to save this file to the user's machine using the filename generated by the server. In Postman, I can actually see that the content-disposition header of the response is set to: Content-Disposition: attachment;filename=myfilename.txt.

I made an attempt to read the content-disposition from the response (see the alert in my JS code), but I always get null (even though the same response shows the content-disposition in Postman).

Am I doing something wrong? Is there a way to retrieve the filename using the fetch response? Is there a better way to get the filename from the server along with the file?

P.S. This is my server-side code for returning the file:

Controller Action

public IHttpActionResult GetFile(){
   return new FileResult("myfilename.txt","Hello World!");
}

FileResult Class

public class FileResult : IHttpActionResult
{
   private string _fileText = "";
   private string _fileName = "";
   private string _contentType = "";

   public FileResult(string name, string text)
   {
       _fileText = text;
       _fileName = name;
       _contentType = "text/plain";
   }

   public Task<HttpResponseMessage> ExecuteActionAsync(CancellationToken token)
   {
        Stream _stream = null;
        if (_contentType == "text/plain")
        {
            var bytes = Encoding.Unicode.GetBytes(_fileText);
            _stream = new MemoryStream(bytes);
        }
        return Task.Run(() =>
        {
            var response = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StreamContent(_stream),
            };

            response.Content.Headers.ContentType = 
                new MediaTypeHeaderValue(_contentType);
            response.Content.Headers.ContentDisposition = 
                new ContentDispositionHeaderValue("attachment")
            {
                FileName = _fileName
            };

            return response;

        }, token);

Edit

My question was specifically about the fetch not the ajax api. Also, in my code, I showed that I was already reading the header from the response exactly like the accepted answer demonstrated on the suggested answer. However, as stated in my post, this solution was not working with fetch.

like image 766
RHarris Avatar asked Mar 14 '18 19:03

RHarris


People also ask

How do you make a GET request in JavaScript using Fetch?

In the first line we use the global fetch() function to send a GET request to our API. The argument of fetch() is the URL with the server-side resource. We then chain the promise with the then() method, which captures the HTTP response in the response argument and call its json() method.

Can we use fetch in JavaScript?

The fetch() method in JavaScript is used to request to the server and load the information on the webpages. The request can be of any APIs that return the data of the format JSON or XML. This method returns a promise.

What is content disposition attachment filename?

Content-Disposition is an optional header and allows the sender to indicate a default archival disposition; a filename. The optional "filename" parameter provides for this. This header field definition is based almost verbatim on Experimental RFC 1806 by R. Troost and S.

How to download a file using JavaScript fetch?

To download a file using Javascript fetch, return the result as a blob, and create a download link to the blob object. fetch("URL") .then((res) => { return res.blob(); })

What is the fetch API in JavaScript?

Using jQuery, you used the cleaner syntax with jQuery.ajax (). Now, JavaScript has its own built-in way to make API requests. This is the Fetch API, a new standard to make server requests with promises, but includes many other features. In this tutorial, you will create both GET and POST requests using the Fetch API.

How do I get the data returned from a fetch request?

The Response object that is returned from 'fetch', has a few methods that let you retrieve the data returned from the request We use the .text () method to get a string of the text from a file. The process is identical if we wanted to retrieve a .csv file and do something with the data that's in the file.

What is the difference between fetch () and catch () methods in JavaScript?

The fetch () method returns a promise. If the promise returned is resolve, the function within the then () method is executed. That function contains the code for handling the data received from the API. Below the then () method, include the catch () method: The API you call using fetch () may be down or other errors may occur.


3 Answers

So, shortly after posting this question, I ran across this issue on Github. It apparently has to do with using CORS.

The suggested work around was adding Access-Control-Expose-Headers:Content-Disposition to the response header on the server.

This worked!

like image 122
RHarris Avatar answered Oct 18 '22 22:10

RHarris


You can extract the filename from the content-disposition header like this:

let filename = '';

fetch(`/url`, { headers: { Authorization: `Bearer ${token}` }}).then((res) => {
    const header = res.headers.get('Content-Disposition');
    const parts = header!.split(';');
    filename = parts[1].split('=')[1];
    return res.blob();
}).then((blob) => {
    // Use `filename` here e.g. with file-saver:
    // saveAs(blob, filename);
});
like image 33
yglodt Avatar answered Oct 19 '22 00:10

yglodt


Decided to post this, as the accepted answer (although helpful to many people) does not actually answer the original question as to how:

"to get the filename from a file downloaded using javascript fetch api?".

One can read the filename (as shown below), and download the file using a similar approach to this (the recommended downloadjs library by this does not get updated anymore; hence, I wouldn't suggest using it). The below also takes into account scenarios where the filename includes unicode characters (i.e.,-, !, (, ), etc.) and hence, comes (utf-8 encoded) in the form of, for instance, filename*=utf-8''Na%C3%AFve%20file.txt (see here for more details). In such cases, the decodeURIComponent() function is used to decode the filename. Working example is given below:

const url ='http://127.0.0.1:8000/'
fetch(url)
    .then(res => {
        const disposition = res.headers.get('Content-Disposition');
        filename = disposition.split(/;(.+)/)[1].split(/=(.+)/)[1];
        if (filename.toLowerCase().startsWith("utf-8''"))
            filename = decodeURIComponent(filename.replace("utf-8''", ''));
        else
            filename = filename.replace(/['"]/g, '');
        return res.blob();
    })
    .then(blob => {
        var url = window.URL.createObjectURL(blob);
        var a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a); // append the element to the dom
        a.click();
        a.remove(); // afterwards, remove the element  
    });

If you are doing a cross-origin request, make sure to set the Access-Control-Expose-Headers response header on server side, in order to expose the Content-Disposition header; otherwise, the filename won't be accessible on client side trhough JavaScript (see furhter documentation here). For instance:

headers = {'Access-Control-Expose-Headers': 'Content-Disposition'}
return FileResponse("Naïve file.txt", filename="Naïve file.txt", headers=headers)
like image 34
Chris Avatar answered Oct 18 '22 22:10

Chris