4.10. ファイルダウンロード¶
4.10.1. Overview¶
Note
コントローラクラスで、ファイルレンダリングのロジックを持たせることは推奨しない。
理由としては、コントローラの役割から逸脱するためである。 また、コントローラから分離することで、Viewの入れ替えが、容易にできる。
ファイルのダウンロード処理の概要を、以下に示す。
DispatchServletは、コントローラへファイルダウンロードのリクエストを送信する。
コントローラは、ファイル表示の情報を取得する。
コントローラは、Viewを選択する。
ファイルレンダリングは、Viewで行われる。
org.springframework.web.servlet.View
インタフェースを提供している。org.springframework.web.servlet.view.document.AbstractPdfView
org.springframework.web.servlet.view.document.AbstractXlsxView
org.terasoluna.gfw.web.download.AbstractFileDownloadView
は、Tip
ファイルダウンロード機能を提供する際には、ディレクトリトラバーサル攻撃への対策が必要な場合がある。 ディレクトリトラバーサル攻撃については、ディレクトリトラバーサル攻撃 を参照すること。
4.10.2. How to use¶
4.10.2.1. PDFファイルのダウンロード¶
org.springframework.web.servlet.view.document.AbstractPdfView
を継承したクラスを作成する必要がある。4.10.2.1.1. カスタムViewの実装¶
AbstractPdfViewを継承したクラスの実装例
@Component // (1)
public class SamplePdfView extends AbstractPdfView { // (2)
@Override
protected void buildPdfDocument(Map<String, Object> model,
Document document, PdfWriter writer, HttpServletRequest request,
HttpServletResponse response) throws Exception { // (3)
document.add(new Paragraph(model.get("serverTime").toString()));
}
}
項番 |
説明 |
---|---|
(1)
|
本例では、
@Component アノテーションを使用して、component-scanの対象としている。後述する、
org.springframework.web.servlet.view.BeanNameViewResolver の対象とすることができる。 |
(2)
|
AbstractPdfView を継承する。 |
(3)
|
buildPdfDocument メソッドを実装する。 |
AbstractPdfView
は、PDFのレンダリングに、OpenPDFを利用している。Note
TERASOLUNA Server Framework for Java 5.4.xではiText 2.1.7をサポートしていたが、後継のiText 5.0.0よりAGPLライセンスに変更されたため、TERASOLUNA Server Framework for Java 5.5.1.RELEASEからiTextからフォークされたOpenPDFをサポートする。
OpenPDFでは、iText 2.1.7からいくつかのバグや脆弱性が修正されている。
<dependencies>
<!-- omitted -->
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
</dependency>
</dependencies>
Note
上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。
Note
iText 2.1.7を利用する場合、日本語の出力を行うためにはiTextAsianを依存ライブラリに追加する必要があったが、OpenPDFはデフォルトで日本語に対応しているため、追加は不要である。
4.10.2.1.2. ViewResolverの定義¶
org.springframework.web.servlet.view.BeanNameViewResolver
とは、
Springのコンテキストで管理されたbean名を用いて実行するViewを選択するクラスである。
BeanNameViewResolver
を使用する際は、通常使用する、
JSP用の
ViewResolver
(InternalResourceViewResolver
)Tiles用の
ViewResolver
(TilesViewResolver
)
より先にBeanNameViewResolver
が実行されるように定義する事を推奨する。
Note
Spring FrameworkはさまざまなViewResolver
を提供しており、複数のViewResolver
をチェーンすることができる。
そのため、特定の状況では、意図しないViewが選択されてしまうことがある。
この動作は、<mvc:view-resolvers>
要素の子要素に、優先したいViewResolver
を上から順に定義する事で防ぐことができる。
bean定義ファイル
<mvc:view-resolvers>
<mvc:bean-name /> <!-- (1) (2) -->
<mvc:jsp prefix="/WEB-INF/views/" />
</mvc:view-resolvers>
項番 |
説明 |
---|---|
(1)
|
<mvc:bean-name> 要素を使用して、BeanNameViewResolver を定義する。 |
(2)
|
<mvc:bean-name> 要素を先頭に定義し、通常使用するViewResolver (JSP用のViewResolver )より優先度を高くする。 |
4.10.2.1.3. コントローラでのViewの指定¶
BeanNameViewResolver
により、コントローラで”samplePdfView”を返却することで、Javaソースコード
@RequestMapping(value = "home", params= "pdf", method = RequestMethod.GET)
public String homePdf(Model model) {
model.addAttribute("serverTime", new Date());
return "samplePdfView"; // (1)
}
項番 |
説明 |
---|---|
(1)
|
“samplePdfView” をメソッドの戻り値として返却することで、
Springのコンテキストで管理された、
SamplePdfView クラスが実行される。 |
4.10.2.2. Excelファイルのダウンロード¶
org.springframework.web.servlet.view.document.AbstractXlsxView
を継承したクラスを作成する必要がある。4.10.2.2.1. カスタムViewの実装¶
AbstractXlsxViewを継承したクラスの実装例
@Component // (1)
public class SampleExcelView extends AbstractXlsxView { // (2)
@Override
protected void buildExcelDocument(Map<String, Object> model,
Workbook workbook, HttpServletRequest request,
HttpServletResponse response) throws Exception { // (3)
Sheet sheet;
Cell cell;
sheet = workbook.createSheet("Spring");
sheet.setDefaultColumnWidth(12);
// write a text at A1
cell = getCell(sheet, 0, 0);
setText(cell, "Spring-Excel test");
cell = getCell(sheet, 2, 0);
setText(cell, model.get("serverTime").toString());
}
private Cell getCell(Sheet sheet, int rowNumber, int cellNumber) {
Row row = sheet.createRow(rowNumber);
return row.createCell(cellNumber);
}
private void setText(Cell cell, String text) {
cell.setCellValue(text);
}
}
項番 |
説明 |
---|---|
(1)
|
本例では、
@Component アノテーションを使用して、component-scanの対象としている。前述した、
org.springframework.web.servlet.view.BeanNameViewResolver の対象とすることができる。 |
(2)
|
AbstractXlsxView を継承する。 |
(3)
|
buildExcelDocument メソッドを実装する。 |
AbstractXlsxView
は、EXCELのレンダリングに、Apache POIを利用している。<dependencies>
<!-- omitted -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>
</dependencies>
Note
上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。
Note
POI 5.1.0 以降のバージョンではApache Log4j v2を依存関係に含んでおり、POIがLog4j 2を直接使用するようになった。
TERASOLUNA Server Framework for Java (5.x)ではロガーのAPIにSLF4Jを使用しているが、APIの優先順位の関係でLog4j 2が有効になる可能性がある。
これを回避するため、log4j-to-slf4j
を依存関係に含むことでSLF4Jへブリッジされるようになる。
<dependencies> <!-- omitted --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-to-slf4j</artifactId> </dependency> </dependencies>
上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。
Warning
SLF4J adapter (log4j-to-slf4j-2.0.jar) とSLF4J bridge (log4j-slf4j-impl-2.0.jar) を一緒に使用すると、SLF4J と Log4j 2の間でイベントが際限なくルーティングされてしまうため注意すること。
詳しくは、Log4j 2 to SLF4J Adapterを参照されたい。
4.10.2.2.2. ViewResolverの定義¶
設定は、PDFファイルをレンダリングする場合と同様である。詳しくは、ViewResolverの定義を参照されたい。
4.10.2.2.3. コントローラでのViewの指定¶
BeanNameViewResolver
により、コントローラで”sampleExcelView”を返却することで、Javaソース
@RequestMapping(value = "home", params= "excel", method = RequestMethod.GET)
public String homeExcel(Model model) {
model.addAttribute("serverTime", new Date());
return "sampleExcelView"; // (1)
}
項番 |
説明 |
---|---|
(1)
|
“sampleExcelView” をメソッドの戻り値として返却することで、
Springのコンテキストで管理された、
SampleExcelView クラスが実行される。 |
4.10.2.3. 任意のファイルのダウンロード¶
org.terasoluna.gfw.web.download.AbstractFileDownloadView
を継承したクラスを実装すればよい。AbstractFileDownloadView
では、以下を実装する必要がある。レスポンスボディへの書き込むためのInputStreamを取得する。
HTTPヘッダに情報を設定する。
4.10.2.3.1. カスタムViewの実装¶
AbstractFileDownloadViewを継承したクラスの実装例
@Component // (1)
public class TextFileDownloadView extends AbstractFileDownloadView { // (2)
@Override
protected InputStream getInputStream(Map<String, Object> model,
HttpServletRequest request) throws IOException { // (3)
Resource resource = new ClassPathResource("abc.txt");
return resource.getInputStream();
}
@Override
protected void addResponseHeader(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) { // (4)
response.setHeader("Content-Disposition",
"attachment; filename=abc.txt");
response.setContentType("text/plain");
}
}
項番 |
説明 |
---|---|
(1)
|
本例では、
@Component アノテーションを使用して、component-scanの対象としている。前述した、
org.springframework.web.servlet.view.BeanNameViewResolver の対象とすることができる。 |
(2)
|
AbstractFileDownloadView を継承する。 |
(3)
|
getInputStream メソッドを実装する。ダウンロード対象の、
InputStream を返却すること。 |
(4)
|
addResponseHeaderメソッド を実装する。ダウンロードするファイルに合わせた、 Content-Dispositionや、ContentTypeを設定する。
|
4.10.2.3.2. ViewResolverの定義¶
設定は、PDFファイルをレンダリングする場合と同様である。詳しくは、ViewResolverの定義を参照されたい。
4.10.2.3.3. コントローラでのViewの指定¶
BeanNameViewResolver
により、コントローラで”textFileDownloadView”を返却することで、Javaソース
@RequestMapping(value = "download", method = RequestMethod.GET)
public String download() {
return "textFileDownloadView"; // (1)
}
項番 |
説明 |
---|---|
(1)
|
“textFileDownloadView” をメソッドの戻り値として返却することで、
Springのコンテキストで管理された、
TextFileDownloadView クラスが実行される。 |
Tip
前述してきたように、SpringはModelの情報をいろいろなViewにレンダリングすることができる。 Springでは、複数のレンダリングエンジンをサポートしており、さまざまなViewを返却することが可能である。 詳細は、Spring の公式ドキュメントSpring Framework Documentation -View Technologies-を参照されたい。