動作環境
Java 17
Spring boot 3.2.1
Gradle
まずは小さく動かしてみる
下記はテキストファイルをダウンロードするためだけのSpringBootのプロジェクトです。
クローン、ビルド、実行して、http://localhost:8080/download
にアクセスすれば直書きした文字列が、txtファイルでダウンロードされるのでお試し下さい。
こちらのソースは『プロになるJava』の著者の一人である山本裕介さんが管理されるLINEオープンチャットにて質問した際に、ご本人から直接ご回答いただいた時のものです。(CC0のため引用)
記事の内容
- ローカルのファイルのダウンロード処理を実装
- テキストファイルのダウンロードが失敗
- Content-Disposition「filename」をURLエンコード
- その他:関連知識
ローカルのファイルをダウンロード処理の実装
いきなり他人まかせですが、きり丸さんの記事から拝借し実装しました。
・実装抜粋
// … Controller処理
Resource resource = new PathResource(path);
return ResponseEntity.ok()
.contentType(getContentType(path))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" +
resource.getFilename() + "\"")
.body(resource);
}
// MediaTyp取得
private MediaType getContentType(
Path path // ダウンロード対象ファイルのパス
) throws IOException {
try {
return MediaType.parseMediaType(Files.probeContentType(path));
} catch (IOException e) {
return MediaType.APPLICATION_OCTET_STREAM;
}
}
記事の通り実装できればjpgやpngなど、テキストファイル以外はダウンロードできます。
(※pdfはブラウザによってはタブで開かれますが、その画面からダウンロードできるので今回はスルーします。)
一方でtxtファイルは「タブ」で開かれかつ文字化けしている状態...
Content-Disposition で設定する fileName に日本語が含む場合はURLエンコードを行う
■機能を最小限にしたプロジェクトを作成し試行
冒頭のプロジェクトで、テキストファイルのダウンロードを試行してみたところ、アルファベットのみのファイル名はダウンロードでき、日本語を含むファイル名は失敗することが判明。
ファイル名の文字コードに起因していると推測されました。早速調べ...
日本語でダウンロードさせるにはURLエンコードが必要になってくる
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(filename, StandardCharsets.UTF_8) + "\"")
そして最終形態
return ResponseEntity.ok()
.contentType(mediaType)
.header(
// Springのhttp系のクラスを利用した形で実装
HttpHeaders.CONTENT_DISPOSITION,
ContentDisposition.attachment()
.filename(filename, StandardCharsets.UTF_8)
.build().toString())
.body(resource);
動確して日本語のテキストファイルもダウンロードできました◎
その他、実装しながら調べたこと
■ Content-Disposition
こちらを参照
今回の関連でいれば、例えば下記
Content-Disposition: attachment; filename="filename.jpg"
attachment:ダウンロードすべきであることを示す
inline:既定値。ウェブページとして表示することを示す
つまり attachment に filename
引数の値を使い、「名前を付けて保存」= ダウンロードするように設定できる。
実装を確認。
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" +
resource.getFilename() + "\"")
なってるじゃないか!たしかに画像ファイルなどはうまくいっている。ということは別の原因ということが分かった
■ MediaType
Webのデータ送受信を行うHTTPなどで、データの種類を表すコード(Content-Type) に設定する種別のこと
ダウンロードできるファイルとできないファイルは何が違うのか...文字化けもあったので、まず気になったのは
コンテントタイプにセットしている「MediaType」、別名 MIMEタイプ
メディア種別 (別名 Multipurpose Internet Mail Extensions または MIME タイプ) は、文書、ファイル、またはバイト列の性質や形式を示します。 MIME タイプは IETF の RFC 6838 で定義され、標準化されています。
ブラウザーは URL を処理する方法を決定するために、ファイル拡張子ではなく MIME タイプを使用しますので、ウェブサーバーは正しい MIME タイプをレスポンスの Content-Type ヘッダーで送信することが重要です。
構造はタイプ/サブタイプ
。
例えば、image/jpeg
やimage/png
、application/pdf
、テキストファイルだとtext/plain
。
(またSpringではMINEに準じるクラスとしてMediaTypeクラスが用意されている。)
MediaType が text/plain
の場合にはダウンロードがうまくいかないので、ここが原因かと思って沼った。。
■ HTTPレスポンス の「body」
※HTTP分からんという人はとりあえずこれ見て
HTTPレスポンスは3つの部品から成り立っています。
1.HTTPステータスライン(ステータスライン)
2.HTTPレスポンスヘッダ(ヘッダ)
3.HTTPレスポンスボディ(レスポンスボディ)
HTTPレスポンスの部品で『ファイルの中身』に当たるところです。
Resource resource = new PathResource(path);
で取得したresource
を直接 body に設定していました。
冒頭のシンプルなプロジェクトでは、バイト配列で文字列をファイルの中身としていました。
なのでresource
を直接渡すのではなく、下記のように変換しても同様の結果でテキストファイルの中身を渡せます。
.body(new ByteArrayResource(resource.getContentAsByteArray()));
※他にも調べましたが、とくに関連があったもののみ備忘録として記載します