Help us understand the problem. What is going on with this article?

ファイルをダウンロードさせる方法(Servlet, HTML, Apache, Tomcat)

More than 1 year has passed since last update.

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

確認したときの環境

  • Firefox50
  • Windows7
  • Java8
    • Servlet v3
  • Apache 2.2
  • Tomcat7, Tomcat8

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\"");
// 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 + "\"");

filenamefilename*の併記

以下のように filenamefilename*を併記すると、filename*が有効な場合はfilenameが無視されます。
filename*に対応していないブラウザを考慮するならば(2017年にそんなブラウザはある?)、併記するのがよさそうです。

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

Therefore, when both "filename" and "filename*" are present in a single header field
value, recipients SHOULD pick "filename*" and ignore "filename".

https://tools.ietf.org/html/rfc6266#section-4.5 引用
ダウンロードファイル名、文字化けとの格闘 参考

filaname*の二重引用符

RFC6266の例では、filenameには二重引用符が付いていますが、filename*には付いていません。

Content-Disposition: attachment;
                     filename="EURO rates";
                     filename*=utf-8''%e2%82%ac%20rates

https://tools.ietf.org/html/rfc6266#section-5 引用

filename*に二重引用符を付けた場合(filename*="utf-8''サンプル.html")、各ブラウザのダウンロード結果は以下の通りになりました。

  • Firefox57: OK
  • Chrome 63:NG
  • Microsoft Edge 41.16299.15.0:NG
  • Internet Explorer 11.125.16299.0:NG

Firefox以外では、filename*を無視され、ファイル名が"download"でした。

https://qiita.com/khsk/items/d541b8dc40bd2c6128d2 参考

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として特別な意味を持つ#なども使えます。
ただしIEは未対応です…

<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

yuji38kwmt
愛知のIT企業で修行しております。2018年4月に転職しました。 基本的に自分用のメモとして、記事を書いております。 所属先の見解とは一切関係ありません。 https://qiita.com/yuji38kwmt/items/a474ad97e0d86f6081a2
kurusugawa
「いいソフトウェアを楽に作る」技術を追求する企業。今は、機械学習、画像認識中心。
http://kurusugawa.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした