web-dev-qa-db-ja.com

Ajax投稿からファイルのダウンロードを処理する

私はajax POSTリクエストを特定のURLに送信するJavaScriptアプリを持っています。応答はJSON文字列か、ファイル(添付ファイルとして)のいずれかです。 ajax呼び出しでContent-TypeとContent-Dispositionを簡単に検出できますが、応答にファイルが含まれていることを検出したら、それをダウンロードするようクライアントに提供するにはどうすればよいですか。私はここで似たようなスレッドをいくつか読んだが、どれも私が探している答えを提供していない。

どうか、答えを投稿しないでください。これにはajaxを使用しないようにするか、ブラウザをリダイレクトするように指示します。これは選択肢の1つではありません。プレーンなHTMLフォームを使用することもできません。必要なのは、ダウンロードダイアログをクライアントに表示することです。これはできますか?

編集:

どうやらこれを行うことはできませんが、受け入れられた答えによって示唆されるように、簡単な回避策があります。将来この問題に遭遇した人のために、私がそれをどのように解決したかは以下のとおりです。

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, request) {
        var disp = request.getResponseHeader('Content-Disposition');
        if (disp && disp.search('attachment') != -1) {
            var form = $('<form method="POST" action="' + url + '">');
            $.each(params, function(k, v) {
                form.append($('<input type="hidden" name="' + k +
                        '" value="' + v + '">'));
            });
            $('body').append(form);
            form.submit();
        }
    }
});

したがって、基本的には、AJAX requestで使用されたのと同じパラメータを使用してHTMLフォームを生成して送信するだけです。

351
Pavle Predic

フォームを作成し、POSTメソッドを使用し、フォームを送信します - iframeは必要ありません。サーバーページがリクエストに応答するとき、ファイルのMIMEタイプのためのレスポンスヘッダーを書くと、ダウンロードダイアログが表示されます - 私はこれを何度もしました。

コンテンツタイプのアプリケーション/ダウンロードが必要です - 使用している言語に関係なくダウンロードを提供する方法を検索するだけです。

105
user571545

これはFileAPIの一部を使用して(最近のブラウザーでは)実行できるため、それほど早くあきらめないでください。

編集2017-09-28:利用可能な場合はFileコンストラクタを使用するように更新したので、Safari> = 10.1で動作します。

編集2015-10-16:jQuery ajaxはバイナリレスポンスを適切に処理できない(responseTypeを設定できない)ので、プレーンなXMLHttpRequest呼び出しを使用することをお勧めします。

var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
    if (this.status === 200) {
        var filename = "";
        var disposition = xhr.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, '');
        }
        var type = xhr.getResponseHeader('Content-Type');

        var blob = typeof File === 'function'
            ? new File([this.response], filename, { type: type })
            : new Blob([this.response], { type: type });
        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {
                    window.location = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params));

これはjQuery.ajaxを使った古いバージョンです。応答がある文字セットの文字列に変換されると、バイナリデータが壊れる可能性があります。

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, xhr) {
        // check for a filename
        var filename = "";
        var disposition = xhr.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, '');
        }

        var type = xhr.getResponseHeader('Content-Type');
        var blob = new Blob([response], { type: type });

        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {
                    window.location = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
});
483
Jonathan Amend

どのサーバー側の言語を使用していますか?私のアプリでは、PHPの応答に正しいヘッダーを設定することでAJAX呼び出しからファイルを簡単にダウンロードできます。

サーバー側でヘッダーを設定する

header("HTTP/1.1 200 OK");
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

// The optional second 'replace' parameter indicates whether the header
// should replace a previous similar header, or add a second header of
// the same type. By default it will replace, but if you pass in FALSE
// as the second argument you can force multiple headers of the same type.
header("Cache-Control: private", false);

header("Content-type: " . $mimeType);

// $strFileName is, of course, the filename of the file being downloaded. 
// This won't have to be the same name as the actual file.
header("Content-Disposition: attachment; filename=\"{$strFileName}\""); 

header("Content-Transfer-Encoding: binary");
header("Content-Length: " . mb_strlen($strFile));

// $strFile is a binary representation of the file that is being downloaded.
echo $strFile;

これは実際にブラウザをこのダウンロードページに「リダイレクト」しますが、@ahren alreadが彼のコメントで述べたように、現在のページから移動することはありません。

正しいヘッダーを設定することがすべてなので、PHPでない場合は、使用しているサーバーサイド言語に適したソリューションを見つけることができるはずです。

レスポンスクライアント側の取り扱い

AJAXの呼び出し方法を既に知っていると仮定して、クライアント側でサーバーにAJAX要求を実行します。次にサーバはこのファイルをダウンロードできる場所からリンクを生成します。あなたが指し示したい「転送」URL。たとえば、サーバーは次のように応答します。

{
    status: 1, // ok
    // unique one-time download token, not required of course
    message: 'http://yourwebsite.com/getdownload/ska08912dsa'
}

レスポンスを処理するとき、あなたはあなたのボディにiframeをインジェクトし、iframeのSRCをあなたがたった今受け取ったURLに設定します(この例の簡単さのためにjQueryを使用します):

$("body").append("<iframe src='" + data.message +
  "' style='display: none;' ></iframe>");

上記のように正しいヘッダーを設定した場合、iframeはブラウザを現在のページから移動させずにダウンロードダイアログを表示します。

注意

あなたの質問に関連して追加のもの。 AJAXテクノロジを持つものを要求するときは、常にJSONを返すのが最善だと思います。 JSONレスポンスを受け取ったら、それを使ってクライアントサイドの処理を決めることができます。たとえば、後でダウンロードを強制するのではなく、後でURLへのダウンロードリンクをクリックしてもらいたい場合は、現在の設定で、クライアント側とサーバー側の両方を更新する必要があります。

30

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

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"リンクに添付してからクリックします。

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

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

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

30
Naren Yellavula

Angularの観点から解決策を探している人のために、これは私のために働いた:

$http.post(
  'url',
  {},
  {responseType: 'arraybuffer'}
).then(function (response) {
  var headers = response.headers();
  var blob = new Blob([response.data],{type:headers['content-type']});
  var link = document.createElement('a');
  link.href = window.URL.createObjectURL(blob);
  link.download = "Filename";
  link.click();
});
20
Tim Hettler

私はあなたがすでに解決策を見つけたと思います、しかし私はただ大きなPOST要求で同じことを達成しようとしている誰かを助けるかもしれないいくつかの情報を加えたいと思いました。

私は2、3週間前に同じ問題を抱えていました、確かにAJAXを通して「きれいな」ダウンロードを達成することは不可能です、Filament Groupはあなたがすでに見つけた方法で動作するjQueryプラグインを作成しました、それは と呼ばれますjQuery File Download しかし、この手法には欠点があります。

あなたがAJAX(例えばファイル+ 1MB)を通して大きなリクエストを送っているなら、それは応答性に悪影響を及ぼすでしょう。遅いインターネット接続では、リクエストが送信されるまでたくさん待つ必要があり、またファイルがダウンロードされるのを待つ必要があります。それはインスタントの "クリック" => "ポップアップ" => "ダウンロード開始"のようなものではありません。 "click" => "データが送信されるまで待つ" => "応答を待つ" => "download start"のようになります。リクエストが送信されるのを待つ必要があるため、ファイルのサイズが2倍になります。 AJAXを介して、ダウンロード可能なファイルとしてそれを取り戻します。

1MB未満の小さいファイルサイズで作業しているのであれば、これに気付かないでしょう。しかし、私が自分のアプリで発見したように、ファイルサイズが大きい場合、それはほとんど耐えられません。

私のアプリはユーザーが動的に生成された画像をエクスポートすることを可能にし、これらの画像はbase64フォーマットでPOSTリクエストを通してサーバーに送られ(それが唯一の方法です)、処理されて.pngの形でユーザーに送り返されます、.jpgファイル、画像+ 1MBのbase64文字列は巨大です、これはファイルがダウンロードを開始するのに必要以上に待つことを強制します。遅いインターネット接続では、それは本当に厄介です。

この問題に対する私の解決策は、ファイルを一時的にサーバーに書き込み、準備ができたら、「しばらくお待ちください」と「ダウンロード」の間で変化するボタンの形でファイルへのリンクを動的に生成することでした。時間が経過したら、プレビューポップアップウィンドウにbase64イメージを印刷して、ユーザーが「右クリック」して保存できるようにします。これはすべての待ち時間をユーザーにとってより耐えられるものにし、そして物事をスピードアップさせます。

2014年9月30日更新:

これを投稿してから数ヶ月が経ちましたが、ようやく大きなbase64文字列を扱うときのスピードを上げるためのより良いアプローチを見つけました。 base64文字列を(longtextまたはlongblogフィールドを使用して)データベースに格納してから、jQueryファイルダウンロードを通じてそのレコードIDを渡し、最後にダウンロードスクリプトファイルでこのIDを使用してデータベースに問い合わせ、base64文字列を取得して渡します。ダウンロード機能.

ダウンロードスクリプトの例:

<?php
// Record ID
$downloadID = (int)$_POST['id'];
// Query Data (this example uses CodeIgniter)
$data       = $CI->MyQueries->GetDownload( $downloadID );
// base64 tags are replaced by [removed], so we strip them out
$base64     = base64_decode( preg_replace('#\[removed\]#', '', $data[0]->image) );
// This example is for base64 images
$imgsize    = getimagesize( $base64 );
// Set content headers
header('Content-Disposition: attachment; filename="my-file.png"');
header('Content-type: '.$imgsize['mime']);
// Force download
echo $base64;
?>

私はこれがOPが尋ねたものを超える方法であることを知っています、しかし私は私の調査結果で私の答えを更新するのが良いと思いました。私が私の問題の解決策を探していたとき、私はたくさん読みました "Download from AJAX POST data " 私が探していた答えを得られなかったスレッド。この情報が誰かがこのようなことを成し遂げようとしているのに役立ちます。

12
José SAYAGO

これが私がどのように動いたかです{ https://stackoverflow.com/a/27563953/2845977

$.ajax({
  url: '<URL_TO_FILE>',
  success: function(data) {
    var blob=new Blob([data]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="<FILENAME_TO_SAVE_WITH_EXTENSION>";
    link.click();
  }
});

download.js を使用して回答を更新しました

$.ajax({
  url: '<URL_TO_FILE>',
  success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});
12
Mayur Padshala

承認された回答でこの手法を使用する場合、つまりフォーム投稿を使用する場合に発生するいくつかの問題点を指摘したいと思います。

  1. リクエストにヘッダを設定することはできません。認証スキーマにヘッダーが含まれている場合は、Json-Web-TokenがAuthorizationヘッダーで渡されます。それを送信するには、他の方法(クエリパラメーターなど)を見つける必要があります。

  2. 要求がいつ終了したのかはわかりません。さて、 jquery.fileDownload によって行われるように、あなたは応答時に設定されるクッキーを使うことができます、しかしそれは完璧からFARです。同時リクエストでは機能せず、レスポンスが届かないと失敗します。

  3. サーバーがエラーで応答した場合、ユーザーはエラーページにリダイレクトされます。

  4. form でサポートされているコンテンツタイプのみを使用できます。つまり、JSONは使えません。

私はS3でファイルを保存し、ファイルを取得するために署名済みのURLを送信するという方法を使用しました。

5
tepez

これは3歳の質問ですが、私は今日同じ問題を抱えていました。私はあなたの編集した解決策を見ました、しかしそれは二重の要求をしなければならないのでそれがパフォーマンスを犠牲にすることができると思います。したがって、誰かがサービスを2回呼び出すことを意味しない別の解決策を必要とするならば、これは私がそれをした方法です:

<form id="export-csv-form" method="POST" action="/the/path/to/file">
    <input type="hidden" name="anyValueToPassTheServer" value="">
</form>

このフォームは単にサービスを呼び出すために使われ、window.location()を使わないようにします。その後は、サービスを呼び出してファイルを取得するために、jqueryからフォームを送信するだけで済みます。それはとても簡単ですが、この方法で _ post _ を使ってダウンロードすることができます。呼び出しているサービスが _ get _ であれば、これはもっと簡単になるかもしれませんが、私の場合はそうではありません。

3
Jairo Miranda

これが一時的な隠しフォームを使った私の解決策です。

//Create an hidden form
var form = $('<form>', {'method': 'POST', 'action': this.href}).hide();

//Add params
var params = { ...your params... };
$.each(params, function (k, v) {
    form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v}));
});

//Make it part of the document and submit
$('body').append(form);
form.submit();

//Clean up
form.remove();

私はJQueryを多用していますが、ネイティブJSでも同じことができます。

2
Ludovic Martin

他の人が言っているように、あなたはPOSTリクエストを通してダウンロードするためのフォームを作成して送信することができます。ただし、これを手動で行う必要はありません。

まさしくこれをするための1つの本当に簡単なライブラリは jquery.redirect です。標準のjQuery.postメソッドに似たAPIを提供します。

$.redirect(url, [values, [method, [target]]])
2
KurtPreston

参照してください: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ それはそれからファイルセーバーに入れることができる応答としてBLOBを返すでしょう

1

私はこれを使いました FileSaver.js 。私のcsvファイルの場合、私はこれを行いました(coffescriptで):

  $.ajax
    url: "url-to-server"
    data: "data-to-send"
    success: (csvData)->
      blob = new Blob([csvData], { type: 'text/csv' })
      saveAs(blob, "filename.csv")

最も複雑な場合は、データを正しく処理する必要があります。その下でFileSaver.jsは Jonathan Amend の答えと同じアプローチを実装しています。

1
Armando

Jonathan Amendsanswer をEdgeで動作させるために、私は以下の変更を行いました。

var blob = typeof File === 'function'
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

これに

var f = typeof File+"";
var blob = f === 'function' && Modernizr.fileapi
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

私はむしろこれをコメントとして投稿したかったのですが、それに対する評判は十分ではありません

1
fstrandner

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

さまざまな情報源から集めた私の解決策は次のとおりです。

    String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
    // Set headers
    response.setHeader("content-disposition", "attachment; filename =" + fileName);
    response.setContentType(contentType);
    // Copy file to output stream
    ServletOutputStream servletOutputStream = response.getOutputStream();
    try (InputStream inputStream = new FileInputStream(file)) {
        IOUtils.copy(inputStream, servletOutputStream);
    } finally {
        servletOutputStream.flush();
        Utils.closeQuitely(servletOutputStream);
        fileToDownload = null;
    }

クライアント側の実装(jqueryを使用)

$.ajax({
type: 'POST',
contentType: 'application/json',
    url: <download file url>,
    data: JSON.stringify(postObject),
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert(errorThrown);
    },
    success: function(message, textStatus, response) {
       var header = response.getResponseHeader('Content-Disposition');
       var fileName = header.split("=")[1];
       var blob = new Blob([message]);
       var link = document.createElement('a');
       link.href = window.URL.createObjectURL(blob);
       link.download = fileName;
       link.click();
    }
});   
0
Dvs Prajapati