日本語を含むマルチバイトで構成されるファイル名のダウンロードを、Spring MVCで実装してみました。ポイントは、ファイル名のURLエンコーディングの要否なのですが、これはブラウザによって扱いが違うのが厄介です。困ったな〜と思ってググっていたら・・・「filename
」と「filename*
」を使えばいいよ!というブログをみつけました。
動作検証バージョン
- Spring Boot 1.3.5.RELEASE
- Spring Framework 4.2.6.RELEASE
Handlerメソッドの実装
細かい説明はしませんが、Handlerメソッドはこんな感じ。
@Autowired
DownloadSupport downloadSupport;
@RequestMapping(path = "/mocks/{id}/file", method = RequestMethod.GET)
public ResponseEntity<Resource> download(@PathVariable int id) throws UnsupportedEncodingException {
MockResponse mockResponse = service.findOne(id); // ファイル名とダウンロードデータ(BLOBデータ)はDBから取得
HttpHeaders headers = new HttpHeaders();
downloadSupport.addContentDisposition(headers, mockResponse.getFileName()); // サポートクラスをみてね!
return ResponseEntity
.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.headers(headers)
.body(new InputStreamResource(mockResponse.getBody()));
}
Supportクラスの実装
Content-Dispositionヘッダを作成するサポートクラスをこんな感じでつくる。「filename
」にファイル名、「filename*
」にURLエンコーディング後のファイル名を設定します。こうすることで、クロスブラウザ対応できてしまう(ようです)!!
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriUtils;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
@Component
public class DownloadSupport {
private static final String CONTENT_DISPOSITION_FORMAT = "attachment; filename=\"%s\"; filename*=UTF-8''%s";
public void addContentDisposition(HttpHeaders headers, String fileName) throws UnsupportedEncodingException {
String headerValue = String.format(CONTENT_DISPOSITION_FORMAT,
fileName, UriUtils.encode(fileName, StandardCharsets.UTF_8.name()));
headers.add(HttpHeaders.CONTENT_DISPOSITION, headerValue);
}
}
ブログではJava SEのURLEncoder
を使っていましたが、半角スペースが「+
」になっちゃったので、Springが提供しているorg.springframework.web.util.UriUtils
を使ってエンコーディングしていますが、これが正しいのかはちゃんと調べてないので、悪しからず。。。。
とりあえず・・・・
- Chrome 51.0.2704.103 (64-bit) on Mac
- Firefox 47.0 on Mac
- Safari 8.0.8 (10600.8.9) on Mac
- IE 11 on Windows 10
- Edge 25 on Window 10
上では、それっぽく動きました。
まとめ
ブラウザのバージョンによっても対応状況が変わりそうな気はしますが・・・、対応するブラウザだけをサポートすればOKなら、この方法で対応するのがよさげです。