JavaScriptのAjaxでバイナリデータをダウンロード
POSTでパラメータを受け、文字コードがSJIS-win(CP932)のCSVファイルをダウンロードとして出力するPHPのページを作ったのですが、このページにAJAX経由からアクセスしようとした際に実装に苦労したので方法を書き残しておきます。
手持ちの資源はjQueryだったので、jQueryの$.Ajaxでの方法を考えたのですがそれでは実現できずにFetchを使うことになりました。
Fetch APIは過去にAJAXについての記事でも触れましたが、近年のブラウザなら標準で利用できます。
PHPのコード
PHPのコードでは次のような感じで、エンコードSJIS、改行コードCRLFのCSVファイルを作成しています。
HTMLのformからこのファイル宛にPOSTすればダウンロードが始まるように設定されています。
ちなみに、ここではCSVファイルを生成していますですが、別記事で紹介しているPhpSpreadsheetを使えばエクセルファイルを生成することもできます。
download-page.php
<?php
// POSTされたパラメータを取得
$params = json_decode(file_get_contents("php://input"),true);
// 2次元配列のデータを取得
$data=getData($params);
// CSVデータ作成
// エクセルで表示できるようにエンコーディングをSJISに
// 改行コードをCRLFに
$contents=mb_convert_encoding("CSVデータ","SJIS-win");
$contents.="\r\n";
for ($i = 0; $i < count($data); $i++) {
for($j =0; $j < count($data[$i]); $j++) {
if ($j != 0) {
$contents.=",";
}
}
$contents.=mb_convert_encoding(str_replace(",","",$data[$i]),"SJIS-win");
}
$contents.="\r\n";
// content-type
header('Content-Type: application/octet-stream');
// ブラウザのMIMEタイプを判断を阻止
header('X-Content-Type-Options: nosniff');
// ファイルのサイズ
header('Content-Length: ' . strlen($contents));
// ファイル名はajax経由の場合jsで名付けます
header('Content-Disposition: attachment; filename="'.date('Y-m-d_H-i-s').'.csv"');
// keep aliveを無効に
header('Connection: close');
// 出力
echo $contents;
exit(0);
AJAXのコード
先のPHPのコードではPOSTされた値を「php://input」で取得するようにしていたので、Content-Typeをapplication/jsonにしています。
JavaScriptのFetch APIのレスポンスは、Promiseで返ります。
Promiseの使い方については過去の記事でも触れましたので合わせて読んでいただけると幸いです。
fetchで得られたレスポンスにはレスポンスの本文をArrayBuffer化するarrayBuffer()メソッドがあり、これを呼ぶとPromiseが連鎖します。
arrayBufferとして得られた値をapplication/octet-streamのBlobでイニシャライズした後、aエレメントにセットしてダウンロードを開始させています。
sample.html
<script>
// AJAXするアドレス
let strAjaxUrl='./download-page.php';
// POSTパラメタ
let params={ key: value };
fetch(strAjaxUrl,{
method: 'post',
headers:{
'Content-Type': 'application/json',
},
body: JSON.stringify(params)
}).then(res=>{
return res.arrayBuffer();
}).then(ab=>{
let blob = new Blob([ab],{type: 'application/octet-stream'});
let a = document.createElement('a');
a.download="ファイル名.csv";
a.href=window.URL.createObjectURL(blob);
a.click();
//ダウンロード後の片付け
blob=null;
a.remove();
}).catch((err) => {
alert(err);
console.error(err);
}).finally(() => {
//成功、失敗問わない最終処理
});
</script>
...