tải xuống tệp bằng yêu cầu ajax


95

Tôi muốn gửi "yêu cầu tải xuống ajax" khi nhấp vào nút, vì vậy tôi đã thử theo cách này:

javascript:

var xhr = new XMLHttpRequest();
xhr.open("GET", "download.php");
xhr.send();

download.php:

<?
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename= file.txt");
header("Content-Transfer-Encoding: binary");    
readfile("file.txt");
?>

nhưng không hoạt động như mong đợi, làm thế nào tôi có thể làm gì? Cảm ơn bạn trước


2
Điều này sẽ không hoạt động, hãy xem [câu hỏi này] [1]. [1]: stackoverflow.com/questions/8771342/…
olegsv

2
Dolocation.href='download.php';
Musa

Câu trả lời:


92

Cập nhật ngày 27 tháng 4 năm 2015

Lên và đến cảnh HTML5 là thuộc tính tải xuống . Nó được hỗ trợ trên Firefox và Chrome và sắp có mặt trên IE11. Tùy thuộc vào nhu cầu của bạn, bạn có thể sử dụng nó thay vì yêu cầu AJAX (hoặc sử dụng window.location) miễn là tệp bạn muốn tải xuống có cùng nguồn gốc với trang web của bạn.

Bạn luôn có thể thực hiện yêu cầu AJAX / window.locationdự phòng bằng cách sử dụng một số JavaScript để kiểm tra xem downloadcó được hỗ trợ không và nếu không, hãy chuyển nó sang lệnh gọi window.location.

Câu trả lời ban đầu

Bạn không thể có yêu cầu AJAX mở lời nhắc tải xuống vì thực tế bạn phải điều hướng đến tệp để nhắc tải xuống. Thay vào đó, bạn có thể sử dụng một hàm thành công để điều hướng đến download.php. Thao tác này sẽ mở lời nhắc tải xuống nhưng sẽ không thay đổi trang hiện tại.

$.ajax({
    url: 'download.php',
    type: 'POST',
    success: function() {
        window.location = 'download.php';
    }
});

Mặc dù điều này trả lời câu hỏi, nhưng tốt hơn là chỉ sử dụng window.locationvà tránh hoàn toàn yêu cầu AJAX.


39
Điều này không gọi liên kết hai lần? Tôi đang ở trong một chiếc thuyền tương tự ... Tôi đang chuyển nhiều thông tin bảo mật trong tiêu đề và có thể phân tích cú pháp đối tượng tệp trong hàm thành công, nhưng không biết cách kích hoạt lời nhắc tải xuống.
user1447679

3
Nó gọi trang hai lần, vì vậy nếu bạn đang truy vấn cơ sở dữ liệu trong trang đó, điều này có nghĩa là 2 lần chuyển đến DB.
mmmmmm

1
@ user1447679 xem giải pháp thay thế: stackoverflow.com/questions/38665947/…
John

1
Hãy để tôi giải thích điều này đã giúp tôi như thế nào ... ví dụ có thể đầy đủ hơn. với "download.php? get_file = true" hoặc thứ gì đó ... Tôi có một hàm ajax thực hiện một số lỗi kiểm tra khi gửi biểu mẫu và sau đó tạo tệp csv. Nếu kiểm tra lỗi không thành công, nó phải quay lại với lý do tại sao nó không thành công. Nếu nó tạo CSV, nó sẽ nói với cha mẹ rằng "hãy tiếp tục và tìm nạp tệp". Tôi làm điều đó bằng cách đăng lên tệp ajax với biến biểu mẫu, sau đó đăng một tham số KHÁC BIỆT lên cùng tệp với nội dung "giao cho tôi tệp bạn vừa tạo" (đường dẫn / tên được mã hóa cứng vào tệp ajax).
Scott

4
Nhưng nó sẽ gửi yêu cầu 2 lần, đó không phải là thích hợp
Dharmendrasinh Chudasama

44

Để làm cho trình duyệt tải xuống một tệp, bạn cần thực hiện yêu cầu như sau:

 function downloadFile(urlToSend) {
     var req = new XMLHttpRequest();
     req.open("GET", urlToSend, true);
     req.responseType = "blob";
     req.onload = function (event) {
         var blob = req.response;
         var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
         var link=document.createElement('a');
         link.href=window.URL.createObjectURL(blob);
         link.download=fileName;
         link.click();
     };

     req.send();
 }

7
Điều này phù hợp với tôi, nhưng trong firefox, trước tiên tôi cần đặt một thẻ <a> vào DOM và tham chiếu nó làm liên kết của tôi thay vì tạo một thẻ ngay lập tức để tệp tự động tải xuống.
Erik Donohoo,

hoạt động nhưng điều gì sẽ xảy ra nếu tệp đang tạo trong quá trình thực thi? nó không hoạt động như khi tệp đã được tạo.
Diego

@Taha Tôi đã thử nghiệm điều này trên Edge và nó có vẻ hoạt động. Tuy nhiên, không biết về IE. Khách hàng của tôi không nhắm mục tiêu người dùng IE ;-) Tôi có may mắn không? : D
Sнаđошƒаӽ

1
@ErikDonohoo Bạn cũng có thể tạo <a>thẻ bằng JS, "on the fly", nhưng phải thêm nó vào document.body. Bạn chắc chắn muốn ẩn nó cùng một lúc.
Márton Tamás

Cảm ơn @ João, tôi đã tìm kiếm giải pháp này từ rất lâu.
Abhishek Gharai

43

Bạn thực sự không cần ajax cho việc này. Nếu bạn chỉ đặt "download.php" làm href trên nút hoặc nếu nó không phải là liên kết, hãy sử dụng:

window.location = 'download.php';

Trình duyệt phải nhận dạng tải xuống nhị phân và không tải trang thực mà chỉ phân phát tệp dưới dạng tải xuống.


3
Ngôn ngữ lập trình bạn đang sử dụng để thay đổi window.location JavaScript.
mikemaccana

1
Bạn nói đúng @mikemaccana, ý tôi thực sự là ajax :).
Jelle Kralt

2
Đã tìm kiếm cao và thấp cho một giải pháp và điều này thật thanh lịch và hoàn hảo. Cảm ơn bạn rất nhiều.
Yangshun Tây

2
Tất nhiên, giải pháp này sẽ chỉ hoạt động nếu nó là một tệp tĩnh đã tồn tại.
krillgar 14/09/17

1
Nếu máy chủ phản hồi có lỗi thì sẽ không có cách nào để ở lại trang chính của bạn mà không bị trình duyệt chuyển hướng đến trang lỗi. Ít nhất thì đây là những gì Chrome làm khi kết quả của window.location trả về 404.
Ian vào

20

Giải pháp trình duyệt chéo, được thử nghiệm trên Chrome, Firefox, Edge, IE11.

Trong DOM, hãy thêm thẻ liên kết ẩn:

<a id="target" style="display: none"></a>

Sau đó:

var req = new XMLHttpRequest();
req.open("GET", downloadUrl, true);
req.responseType = "blob";
req.setRequestHeader('my-custom-header', 'custom-value'); // adding some headers (if needed)

req.onload = function (event) {
  var blob = req.response;
  var fileName = null;
  var contentType = req.getResponseHeader("content-type");

  // IE/EDGE seems not returning some response header
  if (req.getResponseHeader("content-disposition")) {
    var contentDisposition = req.getResponseHeader("content-disposition");
    fileName = contentDisposition.substring(contentDisposition.indexOf("=")+1);
  } else {
    fileName = "unnamed." + contentType.substring(contentType.indexOf("/")+1);
  }

  if (window.navigator.msSaveOrOpenBlob) {
    // Internet Explorer
    window.navigator.msSaveOrOpenBlob(new Blob([blob], {type: contentType}), fileName);
  } else {
    var el = document.getElementById("target");
    el.href = window.URL.createObjectURL(blob);
    el.download = fileName;
    el.click();
  }
};
req.send();

Mã chung tốt. @leo bạn có thể cải thiện mã này bằng cách thêm các tiêu đề tùy chỉnh như thế Authorizationnào không?
vibs2006

1
Cảm ơn @leo. Nó hữu ích. Ngoài ra, bạn đề nghị thêm những gì window.URL.revokeObjectURL(el.href);sau el.click()?
vibs2006,

Tên tệp sẽ bị sai nếu việc bố trí nội dung chỉ định tên tệp không phải UTF8.
Mathew Alden

14

Điều đó là có thể. Bạn có thể bắt đầu tải xuống từ bên trong một hàm ajax, chẳng hạn như ngay sau khi tệp .csv được tạo.

Tôi có một hàm ajax xuất cơ sở dữ liệu liên hệ sang tệp .csv và ngay sau khi hoàn tất, nó sẽ tự động bắt đầu tải xuống tệp .csv. Vì vậy, sau khi tôi nhận được responseText và mọi thứ đều ổn, tôi chuyển hướng trình duyệt như sau:

window.location="download.php?filename=export.csv";

Tệp download.php của tôi trông giống như sau:

<?php

    $file = $_GET['filename'];

    header("Cache-Control: public");
    header("Content-Description: File Transfer");
    header("Content-Disposition: attachment; filename=".$file."");
    header("Content-Transfer-Encoding: binary");
    header("Content-Type: binary/octet-stream");
    readfile($file);

?>

Không có làm mới trang nào và tệp tự động bắt đầu tải xuống.

LƯU Ý - Đã kiểm tra trong các trình duyệt sau:

Chrome v37.0.2062.120 
Firefox v32.0.1
Opera v12.17
Internet Explorer v11

1
Đây không phải là an ninh nguy hiểm-khôn ngoan?
Mickael Bergeron Néron

@ MickaelBergeronNéron Tại sao?
Pedro Sousa

5
Tôi sẽ nghĩ như vậy bởi vì bất kỳ ai cũng có thể gọi download.php? Filename = [something] và thử một số đường dẫn và tên tệp, đặc biệt là những tên phổ biến và điều này thậm chí có thể được thực hiện bên trong một vòng lặp trong một chương trình hoặc một tập lệnh.
Mickael Bergeron Néron

.htaccess sẽ không tránh được điều đó?
Pedro Sousa

9
@PedroSousa .. không. htaccess kiểm soát quyền truy cập vào cấu trúc tệp thông qua Apache. Vì quyền truy cập đã đạt đến một tập lệnh PHP, htaccess bây giờ ngừng nhiệm vụ của nó. Đây RẤT RẤT NHIỀU lỗ hổng bảo mật vì thực sự, bất kỳ tệp nào mà PHP (và người dùng nó đang được chạy dưới đó) có thể đọc, vì vậy nó có thể phân phối vào tệp đọc ... Người ta nên luôn khử trùng tệp được yêu cầu để đọc
Giáo sư


0

Giải mã tên tệp từ tiêu đề phức tạp hơn một chút ...

    var filename = "default.pdf";
    var disposition = req.getResponseHeader('Content-Disposition');

    if (disposition && disposition.indexOf('attachment') !== -1) 
    {
       var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
       var matches = filenameRegex.exec(disposition);

       if (matches != null && matches[1]) 
           filename = matches[1].replace(/['"]/g, '');
    }

3
Vui lòng định dạng toàn bộ khối mã của bạn và cung cấp một số giải thích bổ sung cho quy trình của bạn vì lợi ích của người đọc trong tương lai.
mickmackusa

0

Giải pháp này không khác nhiều so với những giải pháp ở trên, nhưng đối với tôi, nó hoạt động rất tốt và tôi nghĩ nó sạch.

Tôi khuyên bạn nên mã hóa base64 phía máy chủ tệp (base64_encode (), nếu bạn đang sử dụng PHP) và gửi dữ liệu được mã hóa base64 đến máy khách

Trên máy khách, bạn làm điều này:

 let blob = this.dataURItoBlob(THE_MIME_TYPE + "," + response.file);
 let uri = URL.createObjectURL(blob);
 let link = document.createElement("a");
 link.download = THE_FILE_NAME,
 link.href = uri;
 document.body.appendChild(link);
 link.click();
 document.body.removeChild(link);

Mã này đặt dữ liệu được mã hóa vào một liên kết và mô phỏng một cú nhấp chuột vào liên kết, sau đó nó sẽ xóa nó.


Bạn chỉ có thể ẩn thẻ và điền động href vào. không cần thêm và xóa
BPeela

0

Nhu cầu của bạn được đáp ứng. window.location('download.php');
Nhưng tôi nghĩ rằng bạn cần phải chuyển tệp để tải xuống, không phải lúc nào cũng tải xuống cùng một tệp và đó là lý do tại sao bạn đang sử dụng yêu cầu, một tùy chọn là tạo tệp php đơn giản như showfile.php và thực hiện một yêu cầu như

var myfile = filetodownload.txt
var url = "shofile.php?file=" + myfile ;
ajaxRequest.open("GET", url, true);

showfile.php

<?php
$file = $_GET["file"] 
echo $file;

trong đó tệp là tên tệp được chuyển qua Get hoặc Post trong yêu cầu và sau đó bắt phản hồi trong một hàm đơn giản

if(ajaxRequest.readyState == 4){
                        var file = ajaxRequest.responseText;
                        window.location = 'downfile.php?file=' + file;  
                    }
                }

0

có một giải pháp khác để tải xuống một trang web trong ajax. Nhưng tôi đang đề cập đến một trang phải được xử lý trước rồi mới tải xuống.

Trước tiên, bạn cần tách phần xử lý trang khỏi phần tải xuống kết quả.

1) Chỉ các tính toán trang được thực hiện trong lệnh gọi ajax.

$ .post ("CalculusPage.php", {CalculusFunction: true, ID: 29, data1: "a", data2: "b"},

       chức năng (dữ liệu, trạng thái) 
       {
            if (trạng thái == "thành công") 
            {
                / * 2) Trong câu trả lời, trang sử dụng các phép tính trước đó sẽ được tải xuống. Ví dụ, đây có thể là một trang in kết quả của một bảng được tính trong lệnh gọi ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Ví dụ: trong CalculusPage.php

    if (! rỗng ($ _ POST ["CalculusFunction"])) 
    {
        $ ID = $ _POST ["ID"];

        $ query = "INSERT INTO ExamplePage (data1, data2) VALUES ('". $ _ POST ["data1"]. "', '". $ _ POST ["data2"]. "') WHERE id =". $ ID;
        ...
    }

// Ví dụ: trong DownloadPage.php

    $ ID = $ _GET ["ID"];

    $ sede = "CHỌN * TỪ Trang Ví dụ WHERE id =". $ ID;
    ...

    $ filename = "Export_Data.xls";
    header ("Content-Type: application / vnd.ms-excel");
    header ("Nội dung-Bố trí: inline; filename = $ filename");

    ...

Tôi hy vọng giải pháp này có thể hữu ích cho nhiều người, cũng như đối với tôi.


0

Đối với những người tìm kiếm một cách tiếp cận hiện đại hơn, bạn có thể sử dụng fetch API. Ví dụ sau đây cho thấy cách tải xuống tệp bảng tính. Nó được thực hiện dễ dàng với đoạn mã sau.

fetch(url, {
    body: JSON.stringify(data),
    method: 'POST',
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    },
})
.then(response => response.blob())
.then(response => {
    const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
    const downloadUrl = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = downloadUrl;
    a.download = "file.xlsx";
    document.body.appendChild(a);
    a.click();
})

Tôi tin rằng cách tiếp cận này dễ hiểu hơn nhiều so với các XMLHttpRequestgiải pháp khác . Ngoài ra, nó có cú pháp tương tự như jQuerycách tiếp cận, mà không cần thêm bất kỳ thư viện bổ sung nào.

Tất nhiên, tôi khuyên bạn nên kiểm tra xem bạn đang phát triển trình duyệt nào, vì cách tiếp cận mới này sẽ không hoạt động trên IE. Bạn có thể tìm thấy danh sách tương thích đầy đủ của trình duyệt trên liên kết sau .

Quan trọng : Trong ví dụ này, tôi đang gửi một yêu cầu JSON đến một máy chủ đang lắng nghe trên một url. Điều này urlphải được thiết lập, trên ví dụ của tôi, tôi giả sử bạn biết phần này. Ngoài ra, hãy xem xét các tiêu đề cần thiết để yêu cầu của bạn hoạt động. Vì tôi đang gửi JSON, tôi phải thêm Content-Typetiêu đề và đặt nó thành application/json; charset=utf-8, để cho máy chủ biết loại yêu cầu mà nó sẽ nhận được.


0

Giải pháp @Joao Marcos phù hợp với tôi nhưng tôi đã phải sửa đổi mã để làm cho nó hoạt động trên IE, bên dưới nếu mã trông như thế nào

       downloadFile(url,filename) {
        var that = this;
        const extension =  url.split('/').pop().split('?')[0].split('.').pop();

        var req = new XMLHttpRequest();
        req.open("GET", url, true);
        req.responseType = "blob";
        req.onload = function (event) {
            const fileName = `${filename}.${extension}`;
            const blob = req.response;

            if (window.navigator.msSaveBlob) { // IE
                window.navigator.msSaveOrOpenBlob(blob, fileName);
            } 
            const link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);                
            link.download = fileName;
            link.click();
            URL.revokeObjectURL(link.href);

        };

        req.send();
    },
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.