web-dev-qa-db-ja.com

JQuery.Ajaxでファイルをダウンロードする

私はファイルのダウンロードのためにサーバサイドでStruts2アクションを持っています。

<action name="download" class="com.xxx.DownAction">
    <result name="success" type="stream">
        <param name="contentType">text/plain</param>
        <param name="inputName">imageStream</param>
        <param name="contentDisposition">attachment;filename={fileName}</param>
        <param name="bufferSize">1024</param>
    </result>
</action>

しかし、私がjQueryを使ってアクションを呼び出すとき:

$.post(
  "/download.action",{
    para1:value1,
    para2:value2
    ....
  },function(data){
      console.info(data);
   }
);

firebugでは、データは バイナリストリーム で取得されています。 ファイルのダウンロードウィンドウを開く方法を疑問に思います ユーザーがファイルをローカルに保存することができますか?

363
hguser

2019最新のブラウザの更新

これは、現在いくつかの注意事項をお勧めするアプローチです。

  • 比較的最新のブラウザーが必要です
  • ファイルが非常に大きいと予想される場合、元のアプローチ(iframeおよびcookie)と同様の操作を行う必要があります。少なくともダウンロードされるファイルと同じくらいの大きさのシステムメモリ、および/またはその他の興味深いCPUの副作用を消費します。
fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(resp => resp.blob())
  .then(blob => {
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    // the filename you want
    a.download = 'todo-1.json';
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    alert('your file has downloaded!'); // or you know, something with better UX...
  })
  .catch(() => alert('oh no!'));

2012オリジナルのjQuery/iframe/Cookieベースのアプローチ

Bluish はこれについて完全に正しいです。JavaScriptがファイルをユーザーのコンピューターに直接保存できないため(セキュリティ上の懸念から)、Ajaxを介してそれを行うことはできません。残念ながら、ファイルのダウンロード時にメインウィンドウのURLを指すと、ファイルのダウンロードが発生したときのユーザーエクスペリエンスをほとんど制御できなくなります。

jQuery File Download を作成しました。これにより、OnSuccessコールバックとOnFailureコールバックを備えたファイルダウンロードで「Ajaxのような」エクスペリエンスを実現し、より良いユーザーエクスペリエンスを提供します。プラグインが解決する一般的な問題とその使用方法について、私の ブログ投稿 と、 jQuery File Downloadのデモ をご覧ください。 ソース

これは、promiseを使用してプラグイン source を使用する簡単な使用例のデモです。 デモページ には、他にも多くの「より良いUX」の例が含まれています。

$.fileDownload('some/file.pdf')
    .done(function () { alert('File download a success!'); })
    .fail(function () { alert('File download failed!'); });

サポートする必要のあるブラウザに応じて、 https://github.com/eligrey/FileSaver.js/ を使用できる場合があります。これにより、jQuery File Downloadが使用するIFRAMEメソッドよりも明示的な制御が可能になります。

627
John Culviner

誰もこれを投稿していません @ Pekkaの解決策 ...そこで投稿します。それは誰かを助けることができます。

Ajaxを使ってこれを行う必要はありません。ただ使う

window.location="download.action?para1=value1...."
196
bluish

あなたはHTML5でできる

注意:バイナリデータをJSONエンコードできないため、返されるファイルデータはbase64エンコードでなければなりません

私のAJAXレスポンスには、このようなデータ構造があります。

{
    result: 'OK',
    download: {
        mimetype: string(mimetype in the form 'major/minor'),
        filename: string(the name of the file to download),
        data: base64(the binary data as base64 to download)
    }
}

それは私がAJAX経由でファイルを保存するために次のことができることを意味します

var a = document.createElement('a');
if (window.URL && window.Blob && ('download' in a) && window.atob) {
    // Do it the HTML5 compliant way
    var blob = base64ToBlob(result.download.data, result.download.mimetype);
    var url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = result.download.filename;
    a.click();
    window.URL.revokeObjectURL(url);
}

Base64ToBlob関数は here から取得されたもので、この関数に従って使用する必要があります。

function base64ToBlob(base64, mimetype, slicesize) {
    if (!window.atob || !window.Uint8Array) {
        // The current browser doesn't have the atob function. Cannot continue
        return null;
    }
    mimetype = mimetype || '';
    slicesize = slicesize || 512;
    var bytechars = atob(base64);
    var bytearrays = [];
    for (var offset = 0; offset < bytechars.length; offset += slicesize) {
        var slice = bytechars.slice(offset, offset + slicesize);
        var bytenums = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            bytenums[i] = slice.charCodeAt(i);
        }
        var bytearray = new Uint8Array(bytenums);
        bytearrays[bytearrays.length] = bytearray;
    }
    return new Blob(bytearrays, {type: mimetype});
};

あなたのサーバが保存されるフィールドデータをダンプしているなら、これは良いことです。しかし、私はHTML4フォールバックをどのように実装するかについてはあまり詳しく説明していません。

33
Luke Madhanga

1.フレームワークに依存しない:サーブレットが添付ファイルとしてファイルをダウンロード中

<!-- with JS -->
<a href="javascript:window.location='downloadServlet?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>

2. Struts 2フレームワーク:添付ファイルとしてファイルをダウンロードするアクション

<!-- with JS -->
<a href="javascript:window.location='downloadAction.action?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>

<s:a>タグで作成されたURLOGNLで指し示す<s:url>タグを使用することをお勧めします。

<!-- without JS, with Struts tags: THE RIGHT WAY -->    
<s:url action="downloadAction.action" var="url">
    <s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>

上記の場合、 need を使用してContent-Dispositionヘッダーをresponseに書き込み、ファイルをダウンロード(attachment)して開かないように指定します。ブラウザで(inlineContent Typeも指定するには need が必要です。ファイル名と長さを追加することをお勧めします(ブラウザがリアルなプログレスバーを描くのを助けるため)。

たとえば、Zipをダウンロードすると

response.setContentType("application/Zip");
response.addHeader("Content-Disposition", 
                   "attachment; filename=\"name of my file.Zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...

Struts2では(Actionをサーブレットとして使用しているのでなければ、ハック 直接ストリーミングの場合 など)、応答に直接何かを書き込む必要はありません。単に Stream result type を使用してください)それをstruts.xmlで設定してもうまくいきます: EXAMPLE

<result name="success" type="stream">
   <param name="contentType">application/Zip</param>
   <param name="contentDisposition">attachment;filename="${fileName}"</param>
   <param name="contentLength">${fileLength}</param>
</result>

3.フレームワークにとらわれない(/ Struts 2フレームワーク):サーブレット(/ Action)がブラウザ内でファイルを開く

ファイルをダウンロードする代わりにブラウザ内で開く場合は、Content-dispositioninlineに設定する必要がありますが、ターゲットを現在のウィンドウの場所にすることはできません。 javascriptによって作成された新しいウィンドウ、ページ内の<iframe>、または "discussion" target = "_ blank"を指定してオンザフライで作成された新しいウィンドウをターゲティングする必要があります。

<!-- From a parent page into an IFrame without javascript -->   
<a href="downloadServlet?param1=value1" target="iFrameName">
    download
</a>

<!-- In a new window without javascript --> 
<a href="downloadServlet?param1=value1" target="_blank">
    download
</a>

<!-- In a new window with javascript -->    
<a href="javascript:window.open('downloadServlet?param1=value1');" >
    download
</a>
25
Andrea Ligios

私は(@ JohnCulvinerプラグインに触発された)回避策の解決策として小さな機能を作成しました。

// creates iframe and form in it with hidden field,
// then submit form with provided data
// url - form url
// data - data to form field
// input_name - form hidden input name

function ajax_download(url, data, input_name) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" +
                  "<input type=hidden name='" + input_name + "' value='" +
                  JSON.stringify(data) +"'/></form>" +
                  "</body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

クリックイベントを使ったデモ:

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});
22
ndpu

ブラウザにファイルをダウンロードさせる簡単な方法は、次のようなリクエストをすることです。

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

これにより、ブラウザのダウンロードポップアップが開きます。

15
João Marcos

わかりました、ndpuのコードに基づくと、ajax_downloadの改良版(私は思う)です。

function ajax_download(url, data) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" 

    Object.keys(data).forEach(function(key){
        iframe_html += "<input type='hidden' name='"+key+"' value='"+data[key]+"'>";

    });

        iframe_html +="</form></body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

このようにこれを使用してください -

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2});
});

前の例のようにjsonでエンコードされた文字列としてではなく、入力から来る場合のように、適切なpost paramsとしてパラメータが送信されます。

警告:これらのフォームでの可変注入の可能性については注意が必要です。これらの変数をエンコードするためのより安全な方法があるかもしれません。あるいは、それらをエスケープすることを検討してください。

15
Shayne

私は同じ問題に直面し、うまく解決しました。私のユースケースはこれです。

JSONデータをサーバーに送信して、Excelファイルを受け取ります。そのExcelファイルはサーバーによって作成され、クライアントへの応答として返されます。その応答をブラウザのカスタム名のファイルとしてダウンロードする

$("#my-button").on("click", function(){

// Data to post
data = {
    ids: [1, 2, 3, 4, 5]
};

// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    var a;
    if (xhttp.readyState === 4 && xhttp.status === 200) {
        // Trick for making downloadable link
        a = document.createElement('a');
        a.href = window.URL.createObjectURL(xhttp.response);
        // Give filename you wish to download
        a.download = "test-file.xls";
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
    }
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});

上記のスニペットは、次のようにしているだけです。

  • XMLHttpRequestを使用してJSONとして配列をサーバーに送信します。
  • コンテンツをBLOB(バイナリ)として取得した後、ダウンロード可能なURLを作成し、それを目に見えない "a"リンクに添付してクリックします。ここでPOSTリクエストを行いました。代わりに、単純なGETにも行くことができます。 Ajaxでファイルをダウンロードすることはできません。XMLHttpRequestを使用する必要があります。

ここではサーバー側でいくつか注意深く設定する必要があります。私はPython Django HttpResponseにいくつかのヘッダを設定しました。他のプログラミング言語を使用している場合は、それらを適切に設定する必要があります。

# In python Django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

ここでxls(Excel)をダウンロードするので、contentTypeを上記のものに調整しました。ファイルの種類に応じて設定する必要があります。この手法を使用して、あらゆる種類のファイルをダウンロードできます。

13
Naren Yellavula

これが私がしたこと、純粋なjavascriptとhtmlです。テストはしませんでしたが、これはすべてのブラウザで機能するはずです。

JavaScript関数

var iframe = document.createElement('iframe');
iframe.id = "IFRAMEID";
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.src = 'SERVERURL'+'?' + $.param($scope.filtro);
iframe.addEventListener("load", function () {
     console.log("FILE LOAD DONE.. Download should start now");
});

すべてのブラウザでサポートされているコンポーネントだけを使用しても、追加のライブラリはありません。

enter image description hereenter image description here

これが私のサーバーサイドのJava Springコントローラコードです。

@RequestMapping(value = "/rootto/my/xlsx", method = RequestMethod.GET)
public void downloadExcelFile(@RequestParam(value = "param1", required = false) String param1,
    HttpServletRequest request, HttpServletResponse response)
            throws ParseException {

    Workbook wb = service.getWorkbook(param1);
    if (wb != null) {
        try {
            String fileName = "myfile_" + sdf.format(new Date());
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + ".xlsx\"");
            wb.write(response.getOutputStream());
            response.getOutputStream().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }
8
manukyanv07
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}
4

ファイルをダウンロードするための上記の回答にさらにいくつかのことを追加する

以下はバイト配列を生成するJavaのスプリングコードです。

@RequestMapping(value = "/downloadReport", method = { RequestMethod.POST })
    public ResponseEntity<byte[]> downloadReport(
            @RequestBody final SomeObejct obj, HttpServletResponse response) throws Exception {

        OutputStream out = new ByteArrayOutputStream();
        // write something to output stream
        HttpHeaders respHeaders = new HttpHeaders();
        respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        respHeaders.add("X-File-Name", name);
        ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
        return new ResponseEntity<byte[]>(bos.toByteArray(), respHeaders, HttpStatus.CREATED);
    }

今FileSaver.jsを使用してJavaScriptコードで、以下のコードでファイルをダウンロードすることができます

var json=angular.toJson("somejsobject");
var url=apiEndPoint+'some url';
var xhr = new XMLHttpRequest();
//headers('X-File-Name')
xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 201) {
        var res = this.response;
        var fileName=this.getResponseHeader('X-File-Name');
        var data = new Blob([res]);
        saveAs(data, fileName); //this from FileSaver.js
    }
}    
xhr.open('POST', url);
xhr.setRequestHeader('Authorization','Bearer ' + token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'arraybuffer';
xhr.send(json);

上記はファイルをダウンロードします

3

Railsでは、このようにしています。

function download_file(file_id) {
  let url       = '/files/' + file_id + '/download_file';
    $.ajax({
    type: 'GET',
    url: url,
    processData: false,
    success: function (data) {
       window.location = url;
    },
    error: function (xhr) {
     console.log(' Error:  >>>> ' + JSON.stringify(xhr));
    }
   });
 }

トリックは window.location の部分です。コントローラのメソッドは以下のようになります。

# GET /files/{:id}/download_file/
def download_file
    send_file(@file.file,
          :disposition => 'attachment',
          :url_based_filename => false)
end
2
aarkerio

さて、MVCを使用してコントローラからファイルを取得しているときの作業コードは次のとおりです。

バイト配列を宣言してデータを設定しているとしましょう。あなたがする必要があるのはFile関数を使用することです(System.Web.Mvcを使用)。

byte[] bytes = .... insert your bytes in the array
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");

そして、同じコントローラで、それらの2つの関数を追加します

protected override void OnResultExecuting(ResultExecutingContext context)
    {
        CheckAndHandleFileResult(context);

        base.OnResultExecuting(context);
    }

    private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";

    /// <summary>
    /// If the current response is a FileResult (an MVC base class for files) then write a
    /// cookie to inform jquery.fileDownload that a successful file download has occured
    /// </summary>
    /// <param name="context"></param>
    private void CheckAndHandleFileResult(ResultExecutingContext context)
    {
        if (context.Result is FileResult)
            //jquery.fileDownload uses this cookie to determine that a file download has completed successfully
            Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
        else
            //ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
            if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
                Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
    }

そして、あなたはダウンロードして「成功」または「失敗」コールバックを取得するためにあなたのコントローラを呼び出すことができます。

$.fileDownload(mvcUrl('name of the controller'), {
            httpMethod: 'POST',
            successCallback: function (url) {
            //insert success code

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });
1
Yannick Richard

私は実際にはajaxを使用していないが、ダウンロードを要求するためにjavascript呼び出しを使用してからダウンロードが実際に開始されたときにコールバックを取得することを可能にする修正を見つけました。リンクがファイルを送信する前にファイルを作成するために少し時間がかかるサーバーサイドスクリプトを実行するならば、私はこれが役に立ちました。それで、あなたは彼らにそれが処理していることを警告することができます、そしてそれが最終的にファイルを送るときその処理通知を削除します。これが、ファイルが要求されたときと実際にダウンロードが開始されたときに別のイベントが発生するように、最初にajaxを介してファイルをロードしようとした理由です。

フロントページのjs

function expdone()
{
    document.getElementById('exportdiv').style.display='none';
}
function expgo()
{
   document.getElementById('exportdiv').style.display='block';
   document.getElementById('exportif').src='test2.php?arguments=data';
}

iframe

<div id="exportdiv" style="display:none;">
<img src="loader.gif"><br><h1>Generating Report</h1>
<iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe>
</div>

それから他のファイル:

<!DOCTYPE html>
<html>
<head>
<script>
function expdone()
{
    window.parent.expdone();
}
</script>
</head>
<body>
<iframe id="exportif" src="<?php echo "http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe>
<script>document.getElementById('exportif').onload= expdone;</script>
</body></html>

私はjsを使ってget dataを読む方法があると思うのでphpは必要ないでしょう。しかし、私はそれを手にすることを知らないし、私が使っているサーバーはphpをサポートしているのでこれは私のために働く。誰かに役立つ場合に備えて私はそれを共有すると思いました。

0
Kit Ramos

私は長い間この問題に苦しんでいました。最後に、エレガントな外部ライブラリが ここ を提案してくれました。

0

Ajaxの呼び出しではそれができないのは確実です。

ただし、回避策があります。

ステップ:

ファイルのダウンロードにform.submit()を使用している場合は、次のことができます。

  1. クライアントからサーバーへのajax呼び出しを作成し、ファイルストリームをセッション内に保存します。
  2. サーバーから "成功"が返されたら、form.submit()を呼び出して、セッションに格納されているファイルストリームをストリーミングするだけです。

これはform.submit()を作成した後でファイルをダウンロードする必要があるかどうかを決定したい場合に役立ちます。クラッシュした場合は、クライアント側でカスタムメッセージを表示する必要があるかもしれません。そのような場合は、この実装が役に立ちます。

0
Aman Srivastava

window.openhttps://developer.mozilla.org/en-US/docs/Web/API/Window/openを使用してください

たとえば、次のコード行をクリックハンドラに入れることができます。

window.open('/file.txt', '_blank');

( '_blank'ウィンドウ名のために)新しいタブが開き、そのタブでURLが開きます。

サーバー側のコードにも次のようなものがあります。

res.set('Content-Disposition', 'attachment; filename=file.txt');

そうすれば、ブラウザは単にファイルを表示するのではなく、ファイルをディスクに保存するようにユーザーに促します。開いたばかりのタブも自動的に閉じます。

0
Andrew Koster

ajaxでWebページをダウンロードするための別の解決策があります。しかし、最初に処理してからダウンロードする必要があるページを参照しています。

まず、ダウンロード結果からページ処理を切り離す必要があります。

1)ajax呼び出しではページ計算のみが行われます。

 $。post( "CalculusPage.php"、{calculus関数:true、ID:29、data1: "a"、data2: "b"}、
 
 function(data 、status)
 {
 if(status == "success")
 {
/* 2)答えとして、前の計算を使ったページがダウンロードされます。 。例えば、これはajax呼び出しで計算された表の結果を印刷するページにすることができます。 */
 window.location.href = DownloadPage.php + "?ID =" + 29; 
} 
} 
); 
 [。 //例:CalculusPage.php 
 
 if(!empty($ _ POST ["calculusFunction"]))
 {
 $ ID = $ _POST ["ID"]; 
 
 $ query = "ExamplePage(data1、data2)の値に挿入します値( '"。$ _ POST ["data1"]。 "'、 '"。 $ _POST ["data2"]。 "')WHERE id ="。$ ID; 
 ... 
} 
 
 //たとえば、 DownloadPage.php 
 
 $ ID = $ _GET ["ID"]; 
 
 $ sede = "例* WHERE id ="。$ ID; 
 ... 
 
 $ filename = "Export_Data.xls"; 
ヘッダー( "Content-Type:application/vnd.ms-Excel"); [ header( "Content-Disposition:inline; filename = $ filename"); 
 
 ... 

私にとってこの解決策が多くの人に役立つことを願っています。

0
netluke

JQueryファイルのダウンロードを使用したい場合は、IEに注意してください。応答をリセットする必要があります。そうしないとダウンロードされません

    //The IE will only work if you reset response
    getServletResponse().reset();
    //The jquery.fileDownload needs a cookie be set
    getServletResponse().setHeader("Set-Cookie", "fileDownload=true; path=/");
    //Do the reset of your action create InputStream and return

あなたのアクションはgetServletResponse()にアクセスするためにServletResponseAwareを実装することができます

0
Alireza Fattahi