クリックした時に PDF、画像、テキストなどをブラウザ内で開くのではなくダウンロードできるようにする方法

Webサイトからファイルをダウンロードしたい/してもらいたい場合、意図に反してブラウザ上でファイルが開かれてしまっては都合が良くありません。この記事ではクリック時に PDF、画像、テキストなどをブラウザ上で開かず、ダウンロードできるようにする方法を説明します。サーバ側で対応する方法として PHP でサンプルプログラムを示し、クライアント側で対応する方法として download属性について触れます。

サーバ側で対応する (基本)

例えば PHP で以下のようなプログラムを用意します。
仮にこれを download.php とします。


header('Content-Type: application/pdf');
header('Content-Length: '.filesize('sample.pdf'));
header('Content-Disposition: attachment; filename="sample.pdf"');
echo file_get_contents('sample.pdf');
exit;

HTML には以下のように記述します。


<a href="download.php"a>sample.pdf</a>

サーバには sample.pdf をアップロードしておきます。

こうすることで、クリック時に PDF、画像、テキストなどを
ブラウザ内で開くのではなくダウンロードできるようになります。

サーバ側で対応する (実践)

ダウンロード対象のファイルが複数種類、複数個ある場合
を考えますと、先のような作りだとちょっと煩雑かもしれません。

download.php がファイル数分増えると面倒なので、
例えば、以下のようにクエリ (?key=myKey1) で
ダウンロード対象を指定できるように考えてみます。


<a href="download.php?key=myKey1"a>sample.pdf</a>

例えば以下のように 1 なら sample.pdf、
2 なら sample.png といった感じで
リストを作って置けば簡単でしょうか。


$key_2_content = array(
'1' => array(
'path' => './sample.pdf'
),
'2' => array(
'path' => './sample.png'
),
'3' => array(
'path' => './sample.txt'
)
);

1 とか 2 だとちょっと手が滑るなどの軽微な打ち間違いで
間違ったファイルがダウンロードされてしまう可能性がでてきます。
そこで、key には ibrbLaBSPAWiSmis のような
混同の恐れの低いものを割り当てることにして
トラブルになり難いように保険をかけておきます。


$key_2_content = array(
'ibrbLaBSPAWiSmis' => array(
'path' => './sample.pdf'
),
'YPMPeAN2i6ymVxwp' => array(
'path' => './sample.png'
),
'J6XNegQDhdBhTibr' => array(
'path' => './sample.txt'
)
);

サーバ内でのファイル名を知られたくないという話が
後から出てくるとめんどうなので、保険ついでに
ダウンロード時のファイル名と
サーバ内でのファイル名 (=pathの一部) を
独立無関係なものにしておきます。


$key_2_content = array(
'ibrbLaBSPAWiSmis' => array(
'path' => './XYQRuXuVwDdnhbKT',
'name' => 'sample',
'ext' => 'pdf'
),
'YPMPeAN2i6ymVxwp' => array(
'path' => './MT4yK7h7Q2sgPdZX',
'name' => 'sample',
'ext' => 'png'
),
'J6XNegQDhdBhTibr' => array(
'path' => './DAJGVRH5YDM2jWgz',
'name' => 'sample',
'ext' => 'txt'
)
);

以下をクリックすると ./XYQRuXuVwDdnhbKT が
sample.pdf という名前でダウンロードされるという考えです。


<a href="download.php?key=ibrbLaBSPAWiSmis"a>sample.pdf</a>

拡張子から MIMEタイプをさっと得られるように
よく使いそうなものを軽く列挙しておきます。


$ext_2_mime = array(
'txt' => 'text/plain',
'gif' => 'image/gif',
'jpg' => 'image/jpeg',
'png' => 'image/png',
'pdf' => 'application/pdf',
'zip' => 'application/zip'
);

クエリ (key) が規定したものである場合のみ
ファイルがダウンロードされるように簡単にチェックしておきます。
規定外の場合は何もダウンロードされません。


$key = $_REQUEST['key'];
if (!array_key_exists($key, $key_2_content)) { exit; }

パス、ファイル名、MIMEタイプなど、
クエリ (key) に対応するデータを特定します。


$content = $key_2_content[$key];
$path = $content['path'];
$name = $content['name'];
$ext = $content['ext'];
$filename = $name . '.' . $ext;
$mime = $ext_2_mime[$ext];

ダウンロード対象のファイルを返します。


header('Content-Type: ' . $mime);
header('Content-Length: '.filesize($path));
header('Content-Disposition: attachment; filename="'.$filename.'"');
readfile($path); // echo file_get_contents($path); だとメモリを喰います。
exit;

サーバには sample.pdf としてダウンロードさせたいものを
XYQRuXuVwDdnhbKT という名前でアップロードしておきます。

アップロードの際、FTPの転送モードに気をつけて下さい。
ファイルの拡張子から転送モードが自動判定される場合、
Binary ではなく ASCII モードでアップロードされて
問題を生じることがあるかもしれません。

以上、すべてをつなげると以下のようなコードになります。


$key_2_content = array(
'ibrbLaBSPAWiSmis' => array(
'path' => './XYQRuXuVwDdnhbKT',
'name' => 'sample',
'ext' => 'pdf'
),
'YPMPeAN2i6ymVxwp' => array(
'path' => './MT4yK7h7Q2sgPdZX',
'name' => 'sample',
'ext' => 'png'
),
'J6XNegQDhdBhTibr' => array(
'path' => './DAJGVRH5YDM2jWgz',
'name' => 'sample',
'ext' => 'txt'
)
);
$ext_2_mime = array(
'txt' => 'text/plain',
'gif' => 'image/gif',
'jpg' => 'image/jpeg',
'png' => 'image/png',
'pdf' => 'application/pdf',
'zip' => 'application/zip'
);
$key = $_REQUEST['key'];
if (!array_key_exists($key, $key_2_content)) { exit; }
$content = $key_2_content[$key];
$path = $content['path'];
$name = $content['name'];
$ext = $content['ext'];
$filename = $name . '.' . $ext;
$mime = $ext_2_mime[$ext];
header('Content-Type: ' . $mime);
header('Content-Length: '.filesize($path));
header('Content-Disposition: attachment; filename="'.$filename.'"');
readfile($path);
exit;

サンプルを設置してみたので、以下をクリックすると
それぞれサンプルファイルがダウンロードできると思います。

sample.pdf
sample.png
sample.txt

大きなファイルを扱う場合で
out of memoryエラーが出るなら
以下のように出力バッファを無効化したり


while (ob_get_level()) {
ob_end_clean();
}

処理時間が足りなければ
以下のように実行時間の制限を緩和します。


set_time_limit(300); // ゼロに設定すると時間制限がなくなります。

クライアント側で対応する (基本)

以下のように a要素に download属性を付与することで
ファイルをダウンロードするようになります。
ファイルサイズとバッファやプログラムの実行時間など、
プログラム的な考慮事項が少なくてすみます。


<a href="http://www.webmagazine.kakisiti.co.jp/wak-sample/p690/sample.pdf" download="sample.pdf">sample.pdf</a>

ただし、Safari や IE は download属性に対応していません。

以下は download属性によるサンプルです。

sample.pdf
sample.png
sample.txt

クライアント側で対応する (実践)

download属性がサポートされていないブラウザでは
クリックしても基本的にダウンロードされません。
以下のように download属性のサポートをチェックして、
サポートされていない場合には a要素の class属性値に
no-download-attr-support などと付けます。


var hasDownloadAttrSupport = ('download' in document.createElement('a'));
if (!hasDownloadAttrSupport) {
$.each($('a[download]'),function(){
$(this).addClass('no-download-attr-support');
});
}

以下のような CSS を用意することで
注意書きを付置することができます。


a.no-download-attr-support:after { content: '右クリックやロングタップしてダウンロードしてください'; }

より具体的に、多少の位置調整などもすると以下のような感じで書けます。


a { position: relative }
a.no-download-attr-support:after { content: '右クリックやロングタップしてダウンロードしてください'; display: block; position: absolute; left: 0; bottom: -20px; color: gray; font-size: 10px; line-height: 15px; width: 280px; }

すると、以下のように注記を付けることができます。

クリックしてダウンロード

最後に

iOS の Safari など、そもそも設計上、
ファイルをダウンロードして任意のアプリで開く
といった使い方がなじまない
ものもあります。

モバイルまで含めて、使い勝手と
表現の一貫性を追求
するのであれば
クリックでダウンロードという表現自体を
控えた方が良いと言えるかもしれません。

お問い合わせについて

業務として技術コンサルティングやシステム設計・開発を行っております。
気になることがありましたらご相談下さい。
ご相談のみで完結する場合、コンサルティング費用の目安は
内容によりますが1時間で5千円〜1万円ていどです。
コンサルティングや開発を検討されるその前に、
まずはお気軽にコメントやメールでご連絡下さい。
ご契約前のコメントやメールでのやりとりは無料です。

お問い合わせフォーム

お急ぎの場合など、ただちに業務対応が必要な場合は、こちらのお問い合わせフォームをご利用ください。かきしちカンパニーお客様窓口が直ちに対応いたします。
※窓口へのお問い合わせ、お見積もりは無料です。


お名前 (必須)

メールアドレス (必須)

題名

メッセージ本文 (必須)

Share

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*