* サーバ側で作成したPDFの表示とクライアントPCへの保存時のファイル名の指定 [#a9ba6dfa]
#setlinebreak(on)
#contents

関連 : [[サーブレットでファイルダウンロード]]

** やりたいこと [#p3afb117]
+サーバ側でPDFを生成し、そのままブラウザウィンドウ内に表示する。(開く or 保存のダイアログを出さずに)
+表示したPDFファイルをクライアントに保存する時のファイル名を指定する。

#html(<div style="padding-left:10px">)
上記(1) は Content-Disposition に inline を指定するだけで実現可能なので、何も難しい事はない。※ただし Content-Type の指定は必須。
上記(2) はブラウザ環境によって挙動が異なるため、けっこう難問。※調べて分かったが問題なのは IE だけ。
#html(</div>)

#html(<table style="margin:0px 0px 20px 0px;"><tr><td>)
#html(<p style="padding-left:40px;">いったんブラウザに表示したPDFを[Ctrl]+[S]等で保存する時に・・</p>)
#ref(exp1.png,nolink)
#html(</td><td align="left">)
#html(<p style="padding-left:40px;">↓ こうではなくて・・</p>)
#ref(exp2.png,nolink)
#html(<p style="padding-left:40px;">↓ こうなって欲しい</p>)
#ref(exp3.png,nolink)
#html(</td></tr></table>)

** RFC的にはどうなのか [#h4383c1d]
#html(<div style="padding-left:10px">)

これを実現するには、Content-Disposition: inline としながら、filename も指定する事になる。
※Content-Disposition: inline;filename="ファイル名"

でも、この組み合わせはありなのか?
RFCを調べてみた。

https://tools.ietf.org/html/rfc6266#section-4.3
#html(<div style="padding:0px 10px;border:1px solid #333;background:#efe;">)
4.3.  Disposition Parameter: 'Filename'

The parameters "filename" and "filename*", to be matched case-
insensitively, provide information on how to construct a filename for
storing the message payload.

Depending on the disposition type, this information might be used
right away (in the "save as..." interaction caused for the
"attachment" disposition type), or later on (for instance, when the
user decides to save the contents of the current page being
displayed).
#html(</div>)

どうも、&color(red){''あり''}; らしい。
表示した後でも filename パラメータが使用される可能性がある。と書いてある。

#html(</div>)

** 問題点 [#ve7c5791]

+RFC的には問題ない筈なのだが「Content-Disposition: inline;filename="ファイル名"」 を正しく処理してくれないブラウザがある。
※ Content-Disposition が inline の時は、指定した filename が効かない。
+ファイル名のURLエンコードが必要なブラウザと、逆にURLエンコードしない方が良いブラウザがある。
※ これもRFC的にはエンコードするのが正しい筈だが。

** どのブラウザが問題? [#z0cf9c5b]
#html(<div style="padding:0px 0px 10px 10px;">)
Content-Disposition が inline 時に filename が効かないブラウザがどれかを調べてみた。(ついでにファイル名のエンコードも)
※ バージョンが - のものは 2013年2月時点の最新を使用。
※ PDFリーダーは2013年2月時点の最新を使用。
#html(</div>)
|OS|ブラウザ|バージョン|inline時のfilename|日本語ファイル名のエンコード|h
|WinXP|Internet Explorer|6|&color(red){無効};|&color(red){エンコードが必要};|
|WinXP|Internet Explorer|7|&color(red){無効};|&color(green){どちらでもOK};|
|WinXP|Internet Explorer|8|&color(red){無効};|&color(green){どちらでもOK};|
|Win7|Internet Explorer|9|&color(red){無効};|&color(green){どちらでもOK};|
|WinXP/7|Google Chrome|-|&color(green){有効};|&color(green){どちらでもOK};|
|WinXP/7|Mozilla Firefox|-|&color(green){有効};|&color(red){エンコードしちゃダメ};|
|Mac|Safari|-|&color(green){有効};|&color(red){エンコードしちゃダメ};|
|Mac|Google Chrome|-|&color(green){有効};|&color(green){どちらでもOK};|
|Mac|Mozilla Firefox|-|>|面倒くさそうなので省略&br;(アドオン設定が正しくされていればたぶんWindowsと同じ)|

#html(<div style="padding-left:10px;">)
こうやって見ると、やっぱり Google Chrome はかなり優秀。
IEはどのバージョンでも filename 属性は無効。※IE10は調べてないけど変わってるのかな?

※検証に使用したコード : http://www.magata.net/test/download_pdf/

#html(</div>)

** Content-Disposition で指定したfilenameが有効な環境の場合 [#k3406116]
#html(<div style="padding-left:10px;">)
以下のとおり実装する。特に難しい所はなし。
#html(</div>)

#html(<div style="padding-left:10px;">)
*** 実装サンプル(PHPの場合) [#q72eedb7]
#mycode2(){{
 // PDFの作成
 $data = pdfデータを作成する関数();
 
 // PDFファイル名の指定
 pdf_fname = urlencode("xxxx.pdf");
 
 // HTTPヘッダを出力
 header("Content-Type: application/pdf");
 header("Content-Disposition: inline;filename=\"${pdf_fname}\"");
 
 // PDFデータを出力
 echo $data;
 exit;
}}
#html(</div>)

** Content-Disposition で指定したfilenameが無効な環境の場合 [#kd108b65]

#html(<div style="padding-left:10px;">)
この場合は、HTTPヘッダで指定したファイル名でなく、その時に表示しているURLが保存時のファイル名になってしまうので、少し工夫が必要になる。

《案1》
 Content-Disposition に attachment を指定し、いったんダウンロードダイアログを表示する。
 ※いちどダウンロードダイアログを表示すると、filename 属性が有効な状態で開くので、
  開いた後に保存をした場合にも filename で指定したファイル名で保存ができる。

《案2》
 リダイレクトと mode_rewrite などを駆使して、URLが保存時のファイル名になるようにする。

案1で妥協できる場合は、inline を attachment に変えるだけなので、詳細は省略。
以降では、案2について記載するが、ここまでやりますか?という感じがしなくもない。


*** 実装イメージ [#l5e84f98]
#html(<div style="padding:10px 0px 10px 10px;">)
保存時に使用したいファイル名がURLになるように、リクエストを行うようにする(リダイレクトさせる)。
つまり、 xxxx.pdf にアクセスした時に、PDF生成/取得処理が行われるようにする。
ただし、そのまま xxxx.pdf にアクセスすると、xxxx.pdf をそのまま表示してしまうので、
*.pdf  へのリクエストを PDF取得用のプログラムのPATHに動的に変換する。
(下記では mod_rewrite を使用しているが、JAVAなどの場合は web.xml に同じ意味合いの記述を行っても良い。)
#html(</div>)

#ref(image.png,nolink)

#html(<div style="padding-left:40px;">)
上図の(C) の時点で、ブラウザがアクセスしている URL は xxxx.pdf となるので、
表示しているページを保存しようとした場合には、xxxx.pdf というファイル名で保存を行う事ができる。
※ サーバ上にpdfを生成して直接アクセスしても良いけど、マルチユーザを考慮し、動的処理にしたうえでセッションを利用。
※ load_pdf.php で全部やっても良いけど、生成するファイルの内容に応じてファイル名を動的に変えたいケースを考慮して、生成と取得を分けてみた。
#html(</div>)

*** 実装サンプル(PHPの場合) [#q72eedb7]

#html(<div style="padding-left:10px;">)
※デモ・サンプル : http://www.magata.net/test/download_pdf/
#html(</div>)

#html(<p style="padding-left:10px;">create_pdf.php</p>)
#mycode2(){{
 session_start();
 
 // PDFの作成
 $data = PDFを作成する関数();
 
 // PDFをセッションに格納
 $_SESSION["download_pdf"] = $data;
 
 // リダイレクト
 $pdf_fname = urlencode("xxxx.pdf");
 header("Location: " . "http://" . $_SERVER["HTTP_HOST"] . "/test/download_pdf/" . $pdf_fname);
}}
#html(<p style="padding-left:10px;">.htaccess (mod_rewriteの定義)</p>)
#mycode2(){{
 RewriteEngine On
 
 # PATH変換(pdf -> php)
 RewriteRule ^.*\.pdf$ load_pdf.php [L] 
}}
#html(<p style="padding-left:10px;">load_pdf.php</p>)
#mycode2(){{
 session_start();
 
 // HTTPヘッダに指定するPDFファイル名( filenameが有効な環境用)
 $pdf_fname = urlencode("xxxx.pdf");
 
 // セッションに退避しておいたPDFデータを出力
 header("Content-Type: application/pdf");
 header("Content-Disposition: inline;filename=\"${pdf_fname}\"");
 echo $_SESSION["download_pdf"];
 exit;
}}
#html(</div>)

上記までで、とりあえず問題点(1)は解決。


トップ   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS