背景
- Laravel + Vue.jsアプリケーション
- JWT認証のSPA
- つまり、すべてのリクエストの
Authorization
ヘッダにJWTを載せている
axios.defaults.headers.common['Authorization'] = `Bearer ${jwt}`;
- 集計したデータをCSVファイルとしてダウンロードさせたい
- ただし、認可もつけたい
困ったこと
- RESTful APIを叩いているうちは何も問題はない
- CSVダウンロードで困った
<a href="csvダウンロードリンク">...
ではダメ
Authorization
ヘッダが載らないため、認可が通らない
やったこと
サーバ側でCSV生成
- こんな感じのCsvDownloaderクラスを作り、コントローラに注入して使った
- 参考
<?php
declare (strict_types = 1);
namespace App\Http\Controllers\Concerns;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
class CsvDownloader
{
@param
@param
@param
@return
public function downloadCsv(
$header,
$body,
string $fileName
): Response {
$stream = fopen('php://temp', 'w');
fputcsv(
$stream,
collect($header)->toArray()
);
collect($body)->each(
function ($row) use (&$stream) {
fputcsv(
$stream,
$row
);
}
);
rewind($stream);
$csv = str_replace(
PHP_EOL,
"\r\n",
stream_get_contents($stream)
);
$csv= pack('C*',0xEF,0xBB,0xBF) . $csv;
$httpHeaders = [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
];
return response(
$csv,
Response::HTTP_OK,
$httpHeaders
);
}
- 何も考えずにBOMなしUTF-8で出力すると、Excelで開いた時に文字化けしてしまう
- 回避方法は下記のようなものがある:
- フロントエンドとの兼ね合いで、SJISではなくBOMつきUTF-8にした (後述)
フロントエンド側でBlob生成
- CSVデータのダウンロードはAjaxで行い、フロントエンドでBlob化しダウンロードリンクを生成するようにした
- 参考
export default {
...
methods: {
downloadCsvReport() {
axios.get('/download/report')
.then(response => response.data)
.then(data => this.downloadCommon(data, 'filename.csv'));
},
downloadCommon(data, filename) {
const anchor = document.createElement('a');
document.body.appendChild(anchor);
const bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
const blob = new Blob([bom, data], {type: 'text/csv'});
const objectUrl = window.URL.createObjectURL(blob);
anchor.href = objectUrl;
anchor.download = filename;
anchor.click();
window.URL.revokeObjectURL(objectUrl);
}
...
- buttonの
@click
か何かで実行すればCSVファイルをダウンロードできる
- Chromeで動作確認
踏んだ文字化けのパターン
- サーバ側でCSVのエンコーディングをSJISにしてしまった
- フロントエンド側でUTF-8としてデコードし、文字化けしてしまう
- フロントエンド側でBlob生成時にBOMを付与し忘れた
- フロントエンドでHTTPレスポンスをデコードした時点でBOMは外れる
- つまりサーバ側でBOMを付与する必要はない
- が、サーバ単体でまともなCSVファイルをレスポンスできないのは気持ち悪いので付与した