ดาวน์โหลดไฟล์โดยใช้คำขอ ajax


96

ฉันต้องการส่ง "คำขอดาวน์โหลด ajax" เมื่อฉันคลิกที่ปุ่มดังนั้นฉันจึงลองด้วยวิธีนี้:

จาวาสคริปต์:

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");
?>

แต่ไม่ได้ผลตามที่คาดหวังจะทำอย่างไร? ขอบคุณล่วงหน้า


2
ไม่ได้ผลโปรดดู [คำถามนี้] [1] [1]: stackoverflow.com/questions/8771342/…
olegsv

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

คำตอบ:


94

อัปเดต 27 เมษายน 2558

ขึ้นมาและไปที่เกิดเหตุ HTML5 คือดาวน์โหลดแอตทริบิวต์ มันได้รับการสนับสนุนใน Firefox และ Chrome และเร็ว ๆ นี้จะมาถึง IE11 ขึ้นอยู่กับความต้องการของคุณคุณสามารถใช้แทนคำขอ AJAX (หรือใช้window.location) ได้ตราบเท่าที่ไฟล์ที่คุณต้องการดาวน์โหลดมาจากแหล่งที่มาเดียวกับไซต์ของคุณ

คุณก็สามารถทำการร้องขอ AJAX / window.locationทางเลือกโดยใช้JavaScript บางการทดสอบว่าได้รับการสนับสนุนและหากไม่ได้เปลี่ยนไปยังโทรdownloadwindow.location

คำตอบเดิม

คุณไม่สามารถขอ AJAX ให้เปิดพรอมต์ดาวน์โหลดได้เนื่องจากคุณต้องไปที่ไฟล์เพื่อแจ้งให้ดาวน์โหลด คุณสามารถใช้ฟังก์ชันความสำเร็จเพื่อไปที่ download.php เพื่อเปิดพรอมต์ดาวน์โหลด แต่จะไม่เปลี่ยนหน้าปัจจุบัน

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

แม้ว่าสิ่งนี้จะตอบคำถาม แต่ก็ควรใช้window.locationและหลีกเลี่ยงคำขอ AJAX โดยสิ้นเชิง


39
นี่ไม่เรียกลิงค์สองครั้งเหรอ? ฉันอยู่ในเรือลำเดียวกัน ... ฉันกำลังส่งข้อมูลความปลอดภัยจำนวนมากในส่วนหัวและสามารถแยกวิเคราะห์วัตถุไฟล์ในฟังก์ชันความสำเร็จได้ แต่ไม่รู้วิธีเรียกใช้พรอมต์ดาวน์โหลด
user1447679

3
มันเรียกเพจสองครั้งดังนั้นหากคุณกำลังสอบถามฐานข้อมูลในเพจนั้นหมายความว่า 2 ทริปไปยัง DB
mmmmmm

1
@ user1447679 ดูทางเลือกอื่น: stackoverflow.com/questions/38665947/…
John

1
ให้ฉันอธิบายว่าสิ่งนี้ช่วยฉันได้อย่างไร ... ตัวอย่างน่าจะสมบูรณ์กว่านี้ ด้วย "download.php? get_file = true" หรือบางอย่าง ... ฉันมีฟังก์ชัน ajax ที่ตรวจสอบข้อผิดพลาดบางอย่างในการส่งแบบฟอร์มจากนั้นสร้างไฟล์ csv หากการตรวจสอบข้อผิดพลาดล้มเหลวจะต้องกลับมาพร้อมกับสาเหตุที่ล้มเหลว หากสร้างไฟล์ CSV จะเป็นการบอกผู้ปกครองว่า "ดำเนินการดึงไฟล์" ฉันทำได้โดยการโพสต์ไปยังไฟล์ ajax พร้อมตัวแปร form จากนั้นโพสต์พารามิเตอร์ DIFFERENT ไปยังไฟล์เดียวกันโดยบอกว่า "ส่งไฟล์ที่คุณเพิ่งสร้างให้ฉัน" (พา ธ / ชื่อถูกเข้ารหัสในไฟล์ ajax)
Scott

4
แต่จะส่งคำขอ 2 ครั้งนั่นไม่เหมาะสม
Dharmendrasinh Chudasama

45

ในการทำให้เบราว์เซอร์ดาวน์โหลดไฟล์คุณต้องทำการร้องขอดังนี้:

 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
สิ่งนี้ใช้ได้กับฉัน แต่ใน firefox ฉันต้องใส่แท็ก <a> ใน DOM ก่อนและอ้างอิงเป็นลิงก์ของฉันแทนที่จะสร้างขึ้นมาทันทีเพื่อให้ไฟล์ดาวน์โหลดโดยอัตโนมัติ
Erik Donohoo

ใช้งานได้ แต่จะเกิดอะไรขึ้นหากไฟล์กำลังสร้างระหว่างการดำเนินการ มันไม่ทำงานเหมือนกับเมื่อไฟล์ถูกสร้างขึ้นแล้ว
Diego

@Taha ฉันทดสอบสิ่งนี้บน Edge และดูเหมือนว่าจะใช้งานได้ ไม่รู้เกี่ยวกับ IE ลูกค้าของฉันไม่ได้กำหนดเป้าหมายไปที่ผู้ใช้ IE ;-) ฉันโชคดีหรือเปล่า? : D
Sнаđошƒаӽ

1
@ErikDonohoo คุณสามารถสร้าง<a>แท็กด้วย JS "ทันที" ได้เช่นกัน แต่ต้องต่อท้ายdocument.bodyด้วย แน่นอนคุณต้องการซ่อนมันในเวลาเดียวกัน
MártonTamás

ขอบคุณ @ Joãoฉันกำลังมองหาวิธีแก้ปัญหานี้มานานมาก
Abhishek Gharai

44

คุณไม่จำเป็นต้องใช้ ajax เลยสำหรับสิ่งนี้ หากคุณเพียงตั้งค่า "download.php" เป็น href บนปุ่มหรือหากไม่ใช่ลิงก์ให้ใช้:

window.location = 'download.php';

เบราว์เซอร์ควรจดจำการดาวน์โหลดไบนารีและไม่โหลดหน้าเว็บจริง แต่เพียงแค่ให้บริการไฟล์เป็นการดาวน์โหลด


3
ภาษาโปรแกรมที่คุณใช้เปลี่ยนwindow.location คือ JavaScript
mikemaccana

1
คุณพูดถูก @mikemaccana ฉันหมายถึง ajax :)
Jelle Kralt

2
ได้รับการล่าสัตว์สูงและต่ำสำหรับการแก้ปัญหาและนี่คือความสง่างามและสมบูรณ์แบบ ขอบคุณมาก.
Yangshun Tay

2
แน่นอนว่าโซลูชันนี้จะใช้ได้ก็ต่อเมื่อเป็นไฟล์แบบคงที่ที่มีอยู่แล้วเท่านั้น
krillgar

1
หากเซิร์ฟเวอร์ตอบสนองด้วยข้อผิดพลาดแม้ว่าจะไม่มีวิธีใดที่จะอยู่ในหน้าหลักของคุณโดยที่เบราว์เซอร์ไม่เปลี่ยนเส้นทางไปยังหน้าข้อผิดพลาด อย่างน้อยนี่คือสิ่งที่ Chrome ทำเมื่อผลลัพธ์ของ window.location ส่งกลับ 404
Ian

21

โซลูชันเบราว์เซอร์ข้ามทดสอบบน Chrome, Firefox, Edge, IE11

ใน DOM ให้เพิ่มแท็กลิงก์ที่ซ่อนอยู่:

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

จากนั้น:

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();

รหัสทั่วไปที่ดี @leo คุณสามารถปรับปรุงโค้ดนี้ได้โดยการเพิ่มส่วนหัวที่กำหนดเองเช่นAuthorization?
vibs2006

1
ขอบคุณ @leo มันมีประโยชน์คุณแนะนำให้เพิ่มอะไรwindow.URL.revokeObjectURL(el.href);หลังจากนี้el.click()?
vibs2006

ชื่อไฟล์จะไม่ถูกต้องหากการจัดการเนื้อหาระบุชื่อไฟล์ที่ไม่ใช่ UTF8
Mathew Alden

14

มันเป็นไปได้. คุณสามารถเริ่มการดาวน์โหลดจากภายในฟังก์ชัน ajax ตัวอย่างเช่นหลังจากสร้างไฟล์. csv

ฉันมีฟังก์ชัน ajax ที่เอ็กซ์พอร์ตฐานข้อมูลของผู้ติดต่อไปยังไฟล์. csv และหลังจากเสร็จสิ้นมันจะเริ่มดาวน์โหลดไฟล์. csv โดยอัตโนมัติ ดังนั้นหลังจากที่ฉันได้รับ responseText และทุกอย่างก็โอเคฉันเปลี่ยนเส้นทางเบราว์เซอร์ดังนี้:

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

ไฟล์download.phpของฉันมีลักษณะดังนี้:

<?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);

?>

ไม่มีการรีเฟรชหน้าใด ๆ และไฟล์จะเริ่มดาวน์โหลดโดยอัตโนมัติ

หมายเหตุ - ทดสอบในเบราว์เซอร์ต่อไปนี้:

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

1
นี่ไม่ใช่ความปลอดภัยที่เป็นอันตรายหรือ?
Mickael Bergeron Néron

@ MickaelBergeronNéronทำไม?
Pedro Sousa

5
ฉันคิดอย่างนั้นเพราะใคร ๆ ก็สามารถเรียก download.php? filename = [something] และลองใช้เส้นทางและชื่อไฟล์โดยเฉพาะอย่างยิ่งชื่อทั่วไปและสามารถทำได้ภายในลูปภายในโปรแกรมหรือสคริปต์
Mickael Bergeron Néron

จะไม่. htaccess หลีกเลี่ยงสิ่งนั้น?
Pedro Sousa

9
@PedroSousa .. no. htaccess ควบคุมการเข้าถึงโครงสร้างไฟล์ผ่าน Apache เนื่องจากการเข้าถึงเข้าถึงสคริปต์ PHP ตอนนี้ htaccess จึงหยุดปฏิบัติหน้าที่ นี่เป็นช่องโหว่ด้านความปลอดภัยอย่างมากเพราะแน่นอนว่าไฟล์ใด ๆ ที่ PHP (และผู้ใช้กำลังทำงานภายใต้) สามารถอ่านได้ดังนั้นจึงสามารถส่งไปยังไฟล์อ่านได้ ... เราควรล้างไฟล์ที่ร้องขอให้อ่านเสมอ
ศ.


0

การถอดรหัสชื่อไฟล์จากส่วนหัวนั้นซับซ้อนกว่าเล็กน้อย ...

    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
โปรดจัดรูปแบบบล็อกโค้ดทั้งหมดของคุณและให้คำอธิบายเพิ่มเติมเกี่ยวกับกระบวนการของคุณเพื่อประโยชน์ของผู้อ่านในอนาคต
mickmackusa

0

วิธีนี้ไม่แตกต่างจากข้างต้นมากนัก แต่สำหรับฉันแล้วมันใช้ได้ดีมากและฉันคิดว่ามันสะอาด

ฉันขอแนะนำให้ base64 เข้ารหัสฝั่งเซิร์ฟเวอร์ไฟล์ (base64_encode () หากคุณใช้ PHP) และส่งข้อมูลที่เข้ารหัส base64 ไปยังไคลเอนต์

ในไคลเอนต์คุณทำสิ่งนี้:

 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);

รหัสนี้ใส่ข้อมูลที่เข้ารหัสไว้ในลิงก์และจำลองการคลิกที่ลิงก์จากนั้นจึงลบข้อมูลนั้นออก


คุณสามารถทำให้แท็กถูกซ่อนและเติมข้อมูล href แบบไดนามิก ไม่จำเป็นต้องเพิ่มและลบ
BPeela

0

ความต้องการของคุณครอบคลุม window.location('download.php');
แต่ฉันคิดว่าคุณต้องส่งไฟล์ที่จะดาวน์โหลดไม่ใช่ดาวน์โหลดไฟล์เดียวกันเสมอไปและนั่นคือเหตุผลที่คุณใช้คำขอทางเลือกหนึ่งคือการสร้างไฟล์ php ง่ายๆเพียงแค่ showfile.php และ ทำคำขอเช่น

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

showfile.php

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

โดยที่ไฟล์คือชื่อไฟล์ที่ส่งผ่าน Get หรือ Post ในคำขอจากนั้นรับการตอบสนองในฟังก์ชันง่ายๆ

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

0

มีวิธีอื่นในการดาวน์โหลดหน้าเว็บใน ajax แต่ฉันหมายถึงหน้าที่ต้องดำเนินการก่อนแล้วจึงดาวน์โหลด

ก่อนอื่นคุณต้องแยกการประมวลผลเพจออกจากการดาวน์โหลดผลลัพธ์

1) เฉพาะการคำนวณหน้าเท่านั้นที่ทำในการโทร ajax

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

       ฟังก์ชัน (ข้อมูลสถานะ) 
       {
            ถ้า (สถานะ == "สำเร็จ") 
            {
                / * 2) ในคำตอบหน้าที่ใช้การคำนวณก่อนหน้าจะถูกดาวน์โหลด ตัวอย่างเช่นอาจเป็นหน้าที่พิมพ์ผลลัพธ์ของตารางที่คำนวณในการเรียก ajax * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// ตัวอย่างเช่นใน CalculusPage.php

    ถ้า (! empty ($ _ POST ["calculusFunction"])) 
    {
        $ ID = $ _POST ["ID"];

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

// ตัวอย่างเช่นใน DownloadPage.php

    $ ID = $ _GET ["ID"];

    $ sede = "SELECT * FROM ExamplePage WHERE id =". $ ID;
    ...

    $ filename = "Export_Data.xls";
    ส่วนหัว ("Content-Type: application / vnd.ms-excel");
    ส่วนหัว ("Content-Disposition: inline; filename = $ filename");

    ...

ฉันหวังว่าโซลูชันนี้จะเป็นประโยชน์สำหรับหลาย ๆ คนเช่นเดียวกับฉัน


0

สำหรับผู้ที่มองหาแนวทางที่ทันสมัยกว่าคุณสามารถใช้ไฟล์fetch API. ตัวอย่างต่อไปนี้แสดงวิธีดาวน์โหลดไฟล์สเปรดชีต ทำได้อย่างง่ายดายด้วยรหัสต่อไปนี้

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();
})

ฉันเชื่อว่าแนวทางนี้จะเข้าใจง่ายกว่าXMLHttpRequestวิธีแก้ปัญหาอื่น ๆ นอกจากนี้ยังมีไวยากรณ์ที่คล้ายกันกับjQueryแนวทางนี้โดยไม่จำเป็นต้องเพิ่มไลบรารีเพิ่มเติมใด ๆ

แน่นอนฉันขอแนะนำให้ตรวจสอบว่าคุณกำลังพัฒนาเบราว์เซอร์ใดเนื่องจากวิธีการใหม่นี้ใช้ไม่ได้กับ IE คุณสามารถค้นหารายการความเข้ากันได้เบราว์เซอร์เต็มรูปแบบต่อไปนี้การเชื่อมโยง

สำคัญ : ในตัวอย่างนี้ฉันกำลังส่งคำขอ JSON ไปยังเซิร์ฟเวอร์ที่รับฟังไฟล์url. นี้urlจะต้องมีการตั้งค่าในตัวอย่างของฉันฉันสมมติว่าคุณรู้ว่าส่วนนี้ นอกจากนี้ให้พิจารณาส่วนหัวที่จำเป็นสำหรับคำขอของคุณในการทำงาน เนื่องจากฉันกำลังส่ง JSON ฉันต้องเพิ่มContent-Typeส่วนหัวและตั้งค่าapplication/json; charset=utf-8เป็นเพื่อให้เซิร์ฟเวอร์ทราบประเภทของคำขอที่จะได้รับ


0

โซลูชัน @Joao Marcos ใช้งานได้สำหรับฉัน แต่ฉันต้องแก้ไขโค้ดเพื่อให้ทำงานบน IE ด้านล่างหากโค้ดมีลักษณะเป็นอย่างไร

       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();
    },
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.