最終更新日:171127 原本2017-02-13 

ファイルをダウンロードさせたいとき

Tomcat, Apacheを連携してWebシステムで、ファイルをダウンロードさせるときの設定をまとめました。
【確認した環境】

  • Firefox50
  • Windows7

Servletでダウンロード

コンテンツタイプをapplication/octet-streamにする

response.setContentType("application/octet-stream");
response.getWriter().write("Hello World");

https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/MIME_types

これは、バイナリー形式ファイル用の既定の値です。実際は未知のバイナリー形式ファイルを表しており、通常ブラウザーは自動的に実行したり、実行すべきであるかを確認したりしません。これらは Content-Disposition ヘッダーの値が attachment であるかのように扱い、ファイルを '名前を付けて保存' することを提案します。

Content-Dispositionヘッダーの値を"attachment"にする

前述の通り、"application/octet-stream"を指定すればダウンロードできます。
しかし、ダウンロード対象のファイルの種類が分かっている場合、"application/octet-stream"を指定するのは、何だか違和感を感じます。

下記の通り、Content-Dispositionヘッダーに"attachment"を指定すれば、コンテンツタイプが"application/octet-stream"でなくても、ダウンロードできます。

response.setContentType("text/html");
response.setHeader("Content-Disposition","attachment");
response.getWriter().write("Hello World");

ダウンロード時のデフォルトのファイル名

"filename"に指定した値が、ダウンロード時のデフォルトのファイル名になります。

response.setHeader("Content-Disposition","attachment;filename=\"sample.html\"");

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition

When used in combination with Content-Disposition: attachment, it is used as the default filename for an eventual 'Save As" dialog presented to the user.

ファイル名に全角文字が含まれているときは、"filename*"を使います。

String encodedFilename = URLEncoder.encode("サンプル.html", "UTF-8");
response.setHeader("Content-Disposition","attachment;filename*=\"UTF-8''" + encodedFilename + "\"");

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition

The parameters "filename" and "filename*" differ only in that "filename*" uses the encoding defined in RFC 5987. When both "filename" and "filename*" are present in a single header field value, "filename*" is preferred over "filename" when both are present and understood.

半角空白文字はプラス記号「+」に変換されることに、注意してください。
以下のコードは、ファイル名が"a+b.html"になります。

String encodedFilename = URLEncoder.encode("a b.html", "UTF-8");
response.setHeader("Content-Disposition","attachment;filename*=\"UTF-8''" + encodedFilename + "\"");

これは、JavaのURLEncoderの仕様です。
https://docs.oracle.com/javase/jp/8/docs/api/java/net/URLEncoder.html

空白文字「 」はプラス記号「+」に変換されます。

「+」をさらに%20で置換すれば、ファイル名に空白文字が表示されます。
ちなみにURLEncodeは言語によって方言があるので、深入りしないことにします。。。
https://www.glamenv-septzen.net/view/1170

ファイル名に使えない文字はアンダースコアに変換されます。
以下のコードは、ファイル名が_________.htmlになります。

// ¥ / : * ? " < > | を含むファイル名
String encodedFilename = URLEncoder.encode("\\/:*\"<>|.html", "UTF-8");
response.setHeader("Content-Disposition","attachment;filename*=\"UTF-8''" + encodedFilename + "\"");

Apache配下のファイルをダウンロード

mod_headersを有効にして、Content-dispositionヘッダーを指定します。
以下のコードは、Apache配下の全てのxmlファイルがダウンロード対象になります。

LoadModule headers_module modules/mod_headers.so

<FilesMatch "\.(xml)$">
    Header set Content-Disposition attachment
</FilesMatch>

【参考サイト】
http://qiita.com/kompiro/items/ac60721bc43625a057dc

http://sample.co.jp/sample.xmlにアクセスすれば、"sample.xml"というファイル名でダウンロードされます。
URLとしてダウンロードしていることに注意してください。ファイル名にURLとして特別な意味を持つ文字が含まれていると、正しくダウンロードされません。以下の文字を含むファイル名は、正しくダウンロードされませんでした。(Firefoxで確認。これで全てかどうかは不明)

 # %

HTMLのa要素でダウンロード

a要素にdownload属性を指定すると、download属性に指定した値がファイル名となってダウンロードされます。全角文字や半角空白、URLとして特別な意味を持つシャープなども使えます。

<a href="sample.png" download="テストa b#%.png">Download</a>

https://developer.mozilla.org/ja/docs/Web/HTML/Element/a

この属性は、URL に移動するのではなくダウンロードするようブラウザーへ示しますので、ユーザーはローカルファイルとして保存することを促されます。属性に値を指定した場合、保存プロンプトのデフォルトのファイル名として解釈します (ユーザーは必要に応じてファイル名を変更できます)。使用可能な値に制限はありませんが、/ および \ はアンダースコアに変換します。多くのファイルシステムにはファイル名に使用できる文字の制限があり、ブラウザーがファイル名を調整するかもしれません。

<a href="sample.png" download="テストa b#%.png">Download</a>

download属性に ¥ / : * ? " < > | を指定すると、全てアンダースコアに変換されました(Firefoxで確認)。
以下のコードはファイル名が_________.htmlになります。

<a href="sample.png" download="&#x5c;/:*?&quot;&lt;&gt;|.png">Download</a>

Tomcat配下のファイルをダウンロードする際の注意

Tomcat7以前(?)は、URIEncodingのデフォルトはISO-8859-1です。この文字コードはラテンアルファベットのキャラクタセットで、日本語を表現できません。

https://tomcat.apache.org/tomcat-7.0-doc/config/http.html#Common_Attributes

This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, ISO-8859-1 will be used.

そのため、日本語を含むファイルをダウンロードしたいときは、文字コードをUTF-8に設定する必要があります。

server.xml
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443"
           URIEncoding="UTF-8" /><!-- ← 追記 -->

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"
URIEncoding="UTF-8"  /><!-- ← 追記 -->

Tomcat8では、デフォルトがUTF-8なので特に指定は必要ありません。

https://tomcat.apache.org/tomcat-8.0-doc/config/http.html#Common_Attributes

This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, UTF-8 will be used unless the org.apache.catalina.STRICT_SERVLET_COMPLIANCE system property is set to true in which case ISO-8859-1 will be used.

【参考サイト】
http://qiita.com/kazuki43zoo/items/a365d194f5c4df28912f