hasnat Posted October 2, 2018 Share Posted October 2, 2018 (edited) 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 October 2, 2018 by hasnat PHP Code Added Quote Link to comment Share on other sites More sharing options...
requinix Posted October 2, 2018 Share Posted October 2, 2018 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. Quote Link to comment Share on other sites More sharing options...
hasnat Posted October 2, 2018 Author Share Posted October 2, 2018 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... Quote Link to comment Share on other sites More sharing options...
requinix Posted October 2, 2018 Share Posted October 2, 2018 Are you POSTing data through the call? Quote Link to comment Share on other sites More sharing options...
hasnat Posted October 2, 2018 Author Share Posted October 2, 2018 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(); } Quote Link to comment Share on other sites More sharing options...
hasnat Posted October 2, 2018 Author Share Posted October 2, 2018 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; } Quote Link to comment Share on other sites More sharing options...
requinix Posted October 2, 2018 Share Posted October 2, 2018 Actually you aren't sending any data... Is the route configured to only respond over POST? What happens if you go to /download_legal_document/<filename> in your browser? Quote Link to comment Share on other sites More sharing options...
hasnat Posted October 2, 2018 Author Share Posted October 2, 2018 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 Quote Link to comment Share on other sites More sharing options...
requinix Posted October 2, 2018 Share Posted October 2, 2018 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? Quote Link to comment Share on other sites More sharing options...
hasnat Posted October 2, 2018 Author Share Posted October 2, 2018 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 Quote Link to comment Share on other sites More sharing options...
kicken Posted October 2, 2018 Share Posted October 2, 2018 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. Quote Link to comment Share on other sites More sharing options...
hasnat Posted October 2, 2018 Author Share Posted October 2, 2018 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. Quote Link to comment Share on other sites More sharing options...
hasnat Posted October 3, 2018 Author Share Posted October 3, 2018 kicken i think issue is response, @file_get_contents($filename); return a binary string, i used print(readfile($filepath)); and its same, if i change binary to blob dont work, i think blob type has limitation..... Quote Link to comment Share on other sites More sharing options...
kicken Posted October 3, 2018 Share Posted October 3, 2018 (edited) "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 October 3, 2018 by kicken Quote Link to comment Share on other sites More sharing options...
maxxd Posted October 3, 2018 Share Posted October 3, 2018 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. Quote Link to comment Share on other sites More sharing options...
hasnat Posted October 3, 2018 Author Share Posted October 3, 2018 (edited) Dear @kicken, I implimented your code, i am getting a issue, please can you advice Problem with this code is when i use xhrFields: {responseType: 'blob'}, it wont go into .then function , in .then function i used console.log('test'); and it is not work. But when i use xhrFields: {responseType: 'binary'} in .then function console.log('test'); it work. with error shows 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 October 3, 2018 by hasnat Spell Check Quote Link to comment Share on other sites More sharing options...
hasnat Posted October 3, 2018 Author Share Posted October 3, 2018 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); } } Quote Link to comment Share on other sites More sharing options...
hasnat Posted October 3, 2018 Author Share Posted October 3, 2018 xhrFields: {responseType: 'blob'} only works if response type is text, it wont work with files. only binary work but with binary response i am not able to make file download Quote Link to comment Share on other sites More sharing options...
kicken Posted October 4, 2018 Share Posted October 4, 2018 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. Quote Link to comment Share on other sites More sharing options...
hasnat Posted October 4, 2018 Author Share Posted October 4, 2018 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); Quote Link to comment Share on other sites More sharing options...
kicken Posted October 4, 2018 Share Posted October 4, 2018 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. Quote Link to comment Share on other sites More sharing options...
hasnat Posted October 4, 2018 Author Share Posted October 4, 2018 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(); }); Quote Link to comment Share on other sites More sharing options...
hasnat Posted October 4, 2018 Author Share Posted October 4, 2018 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'); Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.