Jump to content

How to download file from ajax response?


hasnat

Recommended Posts

Hi every one,

I am working on web based one page application.
Only accept ajax request.
I want to download file. I managed to send request to server,
on server i got string data from get_file_content, 
Client side in ajax success, i can see binary data in console. 

Problem: I am not able to save file. i need a help how to download file from server via ajax request. I hope i explained my issue well.

I tried alot, Blob is not working, dont know why, when i create blob from response data file saved. when i open, it says no image, or in pdf file corrupted. 

Is there best way to do... 

var ajaxUrl = "<?php echo base_url() ?>download_legal_document/" + file_name;
            console.log(ajaxUrl);
            var ajaxObj = $.ajax({
                method: 'POST',
                url: ajaxUrl,
               processData: false,             
                cache: false,
                xhrFields: {responseType: 'binary'},
                beforeSend: function (xhr) {
                },

success: function (data, status, xhr) {
                    console.log(data);
                    console.log(status);
           
                    console.log(xhr.getResponseHeader("content-type"));
                    blob = new Blob([data], {type: '' + xhr.getResponseHeader("content-type")});

                    a = $('<a />'), url = URL.createObjectURL(blob);
                    a.attr({
                        'href': url,
                        'download': file_name,
                        'text': "click"
                    }).hide().appendTo("body")[0].click();
                    

                }

Above code save file but no data, 

PHP

$filedata = @file_get_contents($path_to_file)

 if ($filedata) {
            // GET A NAME FOR THE FILE
            $basename = basename($filename);

            // THESE HEADERS ARE USED ON ALL BROWSERS
            header("Content-Type: application-x/force-download");
            header("Content-Disposition: attachment; filename=$file_name");
            header("Content-length: " . (string) (strlen($filedata)));
            header("Expires: " . gmdate("D, d M Y H:i:s", mktime(date("H") + 2, date("i"), date("s"), date("m"), date("d"), date("Y"))) . " GMT");
            header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");

            // THIS HEADER MUST BE OMITTED FOR IE 6+
            if (FALSE === strpos($_SERVER["HTTP_USER_AGENT"], 'MSIE ')) {
                header("Cache-Control: no-cache, must-revalidate");
            }

            // THIS IS THE LAST HEADER
            header("Pragma: no-cache");

            // FLUSH THE HEADERS TO THE BROWSER
            flush();

            // WRITE THE FILE
            echo $filedata;
        }

Regards.

 

Edited by hasnat
PHP Code Added
Link to comment
Share on other sites

Easy solution: make sure the server sends a Content-Disposition:attachment header with the response, then instead of using AJAX you redirect the user to that file. The browser will notice the download and not actually navigate away from the current page.

Harder solution: however Mega does it.

Link to comment
Share on other sites

Requinix thanks for your response. I do not have option to redirect, open new page because application is on page, i have to manage from same page. i am using codeigniter and application only accept ajax request and url defined in routes. i defined download_legal_document/" + file_name and response me, if i open a new page with dynamically generated url wont work for me... 

Link to comment
Share on other sites

i am posting data to sever from this code

var ajaxUrl = "<?php echo base_url() ?>download_legal_document/" + file_name;
            console.log(ajaxUrl);
            var ajaxObj = $.ajax({
                method: 'POST',
                url: ajaxUrl,
               processData: false,             
                cache: false,
                xhrFields: {responseType: 'binary'},
                beforeSend: function (xhr) {
                },

success: function (data, status, xhr) {
                    console.log(data);
                    console.log(status);
           
                    console.log(xhr.getResponseHeader("content-type"));
                    blob = new Blob([data], {type: '' + xhr.getResponseHeader("content-type")});

                    a = $('<a />'), url = URL.createObjectURL(blob);
                    a.attr({
                        'href': url,
                        'download': file_name,
                        'text': "click"
                    }).hide().appendTo("body")[0].click();
                    

                }

Link to comment
Share on other sites

On Server i read file using 

$filedata = @file_get_contents($path_to_file)

 if ($filedata) {
            // GET A NAME FOR THE FILE
            $basename = basename($filename);

            // THESE HEADERS ARE USED ON ALL BROWSERS
            header("Content-Type: application-x/force-download");
            header("Content-Disposition: attachment; filename=$file_name");
            header("Content-length: " . (string) (strlen($filedata)));
            header("Expires: " . gmdate("D, d M Y H:i:s", mktime(date("H") + 2, date("i"), date("s"), date("m"), date("d"), date("Y"))) . " GMT");
            header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");

            // THIS HEADER MUST BE OMITTED FOR IE 6+
            if (FALSE === strpos($_SERVER["HTTP_USER_AGENT"], 'MSIE ')) {
                header("Cache-Control: no-cache, must-revalidate");
            }

            // THIS IS THE LAST HEADER
            header("Pragma: no-cache");

            // FLUSH THE HEADERS TO THE BROWSER
            flush();

            // WRITE THE FILE
            echo $filedata;
        }

Link to comment
Share on other sites

echo $filedata; send data back to browser. 

i am using crome and in inspect element, in console, i see, in network tab. i it is image file it shows image from response, if pdf or doc it show code like below. I don't know how to manage this to download. because i have one url in route which accept request and response with data which is below, now next step is to save this data to file with type of data, png, jpg, pdf, doc or xlsx. with response i can see what type of data   console.log(xhr.getResponseHeader("content-type")); which show correctly in console... 

UEsDBBQA/2T4jtHTy5I7lBjzSh5USAbsY4YOv8ADXvteE+NPgF/wp39ouP4ueD4TCmtKLDxtpkI+TUbcn5L9V/57wNhmxy8ZfHzfe+ip5n9cwX1DFP3oa05dV3g/J9OzsttvmKmU/Usf/aODVoz0qxWz7TXmvtd0299/dqKDAjggjnIoKRJRRUd5ew6daS3FxLHb28CGSSSRgqRqBkkk8AAc5NAElFfnv8Z/8Ag4S+Gvw4/am0nwhpFq/iLwRDK1tr/ia2YslvISArWyD/AF0aHO9v4h9zOBu++fC3inTfG/hyx1jR7611LS9TgS5tLu2kEkNxGwyrqw4IIPWgD4z/AODgv/lG3rn/AGGdO/8AR1fgNX78/wDBwX/yjb1z/sM6d/6Or8BqCZH7GeC/+Dan4feKPB2k6nJ8RvG+PvEEcfxG+GXiXVLrXr74Ua+dKs9TOz08UW2/m077f0/w

Link to comment
Share on other sites

Yes, yes, I know the AJAX call is working. That's great to know but we're going to briefly try a different direction.

If you do to /download_legal_document/<filename> what happens? Are you saying that you see "echo $filedata; send data back to browser"? I assume not. Does the browser download the file? Do you see something on the page?

Link to comment
Share on other sites

I attached console screen shots, for image and doc file, a response from server, but when save file, file corrupted. please have a look to response in console.  yes file download but it show for image file that file format is corrupted and for pdf or doc file corrupted

1.png

2.png

Link to comment
Share on other sites

This seems to work for me in the latest Firefox.  I've no idea how it fairs in other browsers/versions.

jQuery(function($){
    $('#start').click(requestContent);
    function requestContent(){
        var xhr;
        $.ajax({
            url: '/gh/get/response.html/zalun/jsFiddleGithubDemo/tree/master/Demo/'
            , method: 'post'
            , xhr: function(){
                xhr = jQuery.ajaxSettings.xhr.apply(this, arguments);
                return xhr;
            }
            , xhrFields: {
                responseType: 'blob'
            }
        }).then(function(){
            var blobUrl = URL.createObjectURL(xhr.response);
            var a = document.createElement('a');
            $(a).attr({
                href: blobUrl
                , download: 'download-demo.html'
            }).text('Click here to download.');

            document.body.appendChild(a);
            a.click();
        });
    }
});

See jsfiddle demo

You can have the browser return your Blob object directly rather than having to construct it.  Doing that with jQuery's ajax code is a bit of a pain though as they do not expose native object or it's .response property so you have to work around that.

I would suggest not hiding your link to download the file, that way the user can manually click it should the automatic .click() call fail or they accidentally cancel the download dialog.

Link to comment
Share on other sites

kicken thnaks for your response.  i am getting problem with 

responseType: 'blob', when i use this option, javascript wont go into then function, 

when i user responseType: 'binary' it work but i give error 

Uncaught TypeError: Failed to execute 'createObjectURL' on 'URL': No function was found that matched the signature provided.

Link to comment
Share on other sites

"binary" isn't a valid option for responseType, so the browser is probably falling back to the default type of "text".  I suspect this is what is causing your corruption issue as the binary data you're returning will get encoded as text.  "blob" is the proper type for binary data.

Look over my example above, make sure you're doing everything correctly.  Post your updated code if you continue to have issues.  You may just be missing a step or have some code in the wrong place.

Also, you should update your PHP code to return a proper mime type for whatever file you're serving, not something made up like application-x/force-download.  If you cannot come up with a specific mime type, then application/octet-stream is the appropriate "unknown data" type.

Edited by kicken
Link to comment
Share on other sites

What I've always done in this situation is used fopen({$loc}, 'w') to write the file to the server, then return a json-encoded array with a boolean true success index as well as a path to the newly created file. Use JavaScript's window.open({ajaxResponse.fileLocation}, '_blank'); to prompt the download. That way the browser doesn't try to redirect the user because it will recognize the download link and not actually open a new tab.

Link to comment
Share on other sites

Dear @kicken, I implimented your code, i am getting a issue, please can you advice 

Problem with this code is

  1. when i use xhrFields: {responseType: 'blob'}, it wont go into .then function ,
  2. in .then function i used  console.log('test');  and it is not work. 
  1. But when i use  xhrFields: {responseType: 'binary'}
  2. in .then function console.log('test'); it work. with error shows 
  3. Uncaught TypeError: Failed to execute 'createObjectURL' on 'URL': No function was found that matched the signature provided.

Below is JS and PHP Code, Any suggestion what i am doing wrong... 

$('#document_list_table').on('click', '.download_file', function (e) {
            e.preventDefault();
            var file_name = $(this).attr('file_name');
            var ajaxUrl = "<?php echo base_url() ?>download_legal_document/" + file_name;
            console.log(ajaxUrl);
            var xhr;
            $.ajax({
                url: ajaxUrl,
                method: 'post',
                xhr: function () {
                    xhr = jQuery.ajaxSettings.xhr.apply(this, arguments);
                    return xhr;
                },
                xhrFields: {responseType: 'blob'}
            }).then(function () {
              console.log('test');
                var blobUrl = URL.createObjectURL(xhr.response);
                var a = document.createElement('a');
                $(a).attr({
                    href: blobUrl, 
                  	download: file_name
                }).text('Click here to download.');
                $('#document_list_table_area').append(a);
            });
        });
Quote

PHP Code which send response

$filedata = @file_get_contents($file_path);
if ($filedata) {
    $basename = basename($filename);
    header('Content-Type: ' . $this->get_mine_type($file_name));
    header("Content-Disposition: attachment; filename=$file_name");
    header("Content-length: " . (string) (strlen($filedata)));
    header("Expires: " . gmdate("D, d M Y H:i:s", mktime(date("H") + 2, date("i"), date("s"), date("m"), date("d"), date("Y"))) . " GMT");
    header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
    if (FALSE === strpos($_SERVER["HTTP_USER_AGENT"], 'MSIE ')) {
    	header("Cache-Control: no-cache, must-revalidate");
	}
    header("Pragma: no-cache");
    flush();
    echo $filedata;
}

 

Edited by hasnat
Spell Check
Link to comment
Share on other sites

Dear @kicken,

I used following method

it shows download but with no data, corrupted,

if i remove .httaccess file and provide direct link it works,

but with no direct access no chance, i used following code but no chance any suggestion 

HTML

<a href="<?php echo base_url('download_legal_document') ?>/<?php echo $filename; ?>" download="<?php echo $filename; ?>" class="btn btn-xs btn-green">Download</a>

PHP

 public function download_legal_document($file_name) {
        
       
         $file_path = FCPATH . 'hrmUploadedData/' . $path . $file_name;

        if ($filedata) {
            header('Content-Type: application/octet-stream');
            header('Content-Description: File Transfer');
            header('Content-Disposition: attachment; filename=' . basename($file_name));
            header('Content-Transfer-Encoding: binary');
            header('Expires: 0');
            header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
            header('Pragma: public');
            header('Content-Length: ' . filesize($file_path));
            ob_clean();
            flush();
            print(readfile($file));
           
        } else {
            trigger_error("ERROR: UNABLE TO OPEN $file_name", E_USER_ERROR);
        }
    }

 

Link to comment
Share on other sites

print(readfile($file));

You shouldn't be using print there.  Just readfile($file);.  The readfile function outputs the contents directly rather than returning it.  Having the print may be introducing extra data that corrupts the file.

What's wrong with your direct link method of:

<a href="<?php echo base_url('download_legal_document') ?>/<?php echo $filename; ?>" download="<?php echo $filename; ?>" class="btn btn-xs btn-green">Download</a>

You mentioned earlier something about it having to be an ajax request, but is that because that's all the server allows or do you just think that because of your single-page app behavior?  If the server only allows an ajax request, i'd look into changing the server configuration so your direct link works as it'd be much simpler and more compatible.

Link to comment
Share on other sites

Dear Kicken,

Thanks for your concern. I just found the issue, issue is with my code. i used hook before all code igniter controller to check if request is ajax then allow otherwise block. system is working fine except when i use XMLHttpRequest for download.

if i remove ajax check following code works. But ajax check is important for application i cannot remove. i am trying to find a secure way to solve it, right now no idea how, if you have any suggestion or idea to deal with this scenario, it will be great 

 

Hook

if (!$this->is_logged_in() && $this->input->is_ajax_request()) {
  echo json_encode(array('response' => 'session_expired'));
  exit();
} else if (!$this->is_logged_in()) {
  redirect('login');
  exit();
} else if (!$this->input->is_ajax_request()) {
  redirect('backend');
  exit();
// if i remove below code works, otherwise no
}
var ajax = new XMLHttpRequest();
            ajax.open("POST", ajaxUrl, true);
            ajax.onreadystatechange = function () {
                if (this.readyState == 4) {
                    if (this.status == 200) {
                        console.log(typeof this.response); // should be a blob
                        var blob = new Blob([this.response], {type: "application/octet-stream"});

                        saveAs(blob, file_name);
                    } else if (this.responseText != "") {
                        console.log(this.responseText);
                    }
                } else if (this.readyState == 2) {
                    if (this.status == 200) {
                        this.responseType = "blob";
                    } else {
                        this.responseType = "text";
                    }
                }
            };
            ajax.send(null);

 

Link to comment
Share on other sites

I don't know code igniter so I can't tell you how, but you could just skip the ajax request check if the requested URL is for a file in the /download_legal_document/ folder.  $_SERVER['REQUEST_URI'] could be helpful unless code igniter has a better method of checking the requested URL.

 

Link to comment
Share on other sites

Solved by adding  ajax.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

 

$('#document_list_table').on('click', '.download_file', function (e) {
            e.preventDefault();
            var file_name = $(this).attr('file_name');
            var ajaxUrl = "<?php echo base_url() ?>download_legal_document/" + file_name;
            console.log(ajaxUrl);
            var ajax = new XMLHttpRequest();
           
            ajax.open("POST", ajaxUrl, true);
            ajax.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            ajax.onreadystatechange = function () {
                if (this.readyState == 4) {
                    if (this.status == 200) {
                        console.log(this.response);
                        console.log(typeof this.response); // should be a blob
                        var blob = new Blob([this.response], {type: "application/octet-stream"});

                        saveAs(blob, file_name);
                    } else if (this.responseText != "") {
                        console.log(this.responseText);
                    }
                } else if (this.readyState == 2) {
                    if (this.status == 200) {
                        this.responseType = "blob";
                    } else {
                        this.responseType = "text";
                    }
                }
            };
            ajax.send();

        });

 

Link to comment
Share on other sites

4 hours ago, kicken said:

I don't know code igniter so I can't tell you how, but you could just skip the ajax request check if the requested URL is for a file in the /download_legal_document/ folder.  $_SERVER['REQUEST_URI'] could be helpful unless code igniter has a better method of checking the requested URL.

 

Solved by adding  ajax.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.