ファイルダウンロード
================================================================================
.. only:: html
.. contents:: 目次
:depth: 3
:local:
|
Overview
--------------------------------------------------------------------------------
| 本節では、クライアントにサーバからファイルをダウンロードする機能について説明する。
| ファイルのダウンロード処理の概要を、以下に示す。
#. DispatcherServletは、コントローラへファイルダウンロードのリクエストを送信する。
#. コントローラは、ファイル表示の情報を取得する。
#. コントローラは、Viewを選択する。
#. ファイルレンダリングは、Viewで行われる。
本ガイドラインでは、共通ライブラリから提供している\ ``org.terasoluna.gfw.web.download.AbstractFileDownloadView``\ を継承したカスタムViewを実装することを推奨する。
.. tip::
ファイルダウンロード機能を提供する際には、ディレクトリトラバーサル攻撃への対策が必要な場合がある。
ディレクトリトラバーサル攻撃については、\ :ref:`file-upload_security_related_warning_points_directory_traversal` \ を参照すること。
|
How to use
--------------------------------------------------------------------------------
.. _FileDownloadViewResolverConfiguration:
ViewResolverの設定
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| カスタムViewクラスを用いてファイルをダウンロードするため、jspやthymeleafのような画面名ではなくBean名を指定する必要がある。
| blankプロジェクトから生成されたプロジェクトでは、Springのコンテキストで管理されたBean名を用いて実行するViewを選択できるように\ ``org.springframework.web.servlet.view.BeanNameViewResolver``\ が以下のように設定されている。
.. tabs::
.. group-tab:: Java Config
.. tabs::
.. group-tab:: JSP
\ **SpringMvcConfig.java**\
.. code-block:: java
@EnableAspectJAutoProxy
@EnableWebMvc
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
// omitted
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.beanName(); // (1)
registry.jsp("/WEB-INF/views/", ".jsp");
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ``ViewResolverRegistry#beanName``\ を呼び出し、\ ``BeanNameViewResolver``\ を定義する。
| JSP用の\ ``ViewResolver``\ より先に定義することで、\ ``BeanNameViewResolver``\ の優先度を高くする。
.. group-tab:: Thymeleaf
\ **SpringMvcConfig.java**\
.. code-block:: java
@EnableAspectJAutoProxy
@EnableWebMvc
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
// omitted
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.beanName(); // (1)
registry.viewResolver(thymeleafViewResolver());
}
@Bean
public ThymeleafViewResolver thymeleafViewResolver() {
// omitted
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ``ViewResolverRegistry#beanName``\ を呼び出し、\ ``BeanNameViewResolver``\ を定義する。
| Thymeleaf用の\ ``ViewResolver``\ より先に定義することで、\ ``BeanNameViewResolver``\ の優先度を高くする。
.. group-tab:: XML Config
.. tabs::
.. group-tab:: JSP
\ **spring-mvc.xml**\
.. code-block:: xml
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ````\ 要素を使用して、\ ``BeanNameViewResolver``\ を定義する。
| JSP用の\ ``ViewResolver``\ より先に定義することで、\ ``BeanNameViewResolver``\ の優先度を高くする。
.. group-tab:: Thymeleaf
\ **spring-mvc.xml**\
.. code-block:: xml
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ````\ 要素を使用して、\ ``BeanNameViewResolver``\ を定義する。
| Thymeleaf用の\ ``ViewResolver``\ より先に定義することで、\ ``BeanNameViewResolver``\ の優先度を高くする。
.. note::
Spring Frameworkはさまざまな\ ``ViewResolver``\ を提供しており、複数の\ ``ViewResolver``\ をチェーンすることができる。
ただし、読み込み順によっては意図しないViewが選択されてしまうことがある。
この動作は、\ ````\ 要素の子要素や\ ``ViewResolverRegistry``\ に、優先したい\ ``ViewResolver``\ を上から順に定義する事で防ぐことができる。
|
ファイルのダウンロード
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| 共通ライブラリが提供している\ ``org.terasoluna.gfw.web.download.AbstractFileDownloadView``\ を継承したクラスを実装する。
| \ ``AbstractFileDownloadView``\ では、以下を実装する必要がある。
1. レスポンスボディへの書き込むためのInputStreamを取得する。
2. HTTPヘッダに情報を設定する。
|
カスタムViewの実装
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
| テキストファイルをダウンロードする例を用いて、説明を行う。
\ **AbstractFileDownloadViewを継承したクラスの実装例**\
.. code-block:: java
@Component // (1)
public class TextFileDownloadView extends AbstractFileDownloadView { // (2)
@Override
protected InputStream getInputStream(Map model, HttpServletRequest request)
throws IOException { // (3)
Resource resource = new ClassPathResource("testdata/サンプル.txt");
return resource.getInputStream();
}
@Override
protected void addResponseHeader(Map model, HttpServletRequest request,
HttpServletResponse response) { // (4)
String encodedFileName = URLEncoder.encode("サンプル.txt", StandardCharsets.UTF_8);
String contentDisposition =
String.format("attachment; filename*=UTF-8''%s", encodedFileName);
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", contentDisposition); // (5)
response.setContentType("text/plain"); // (6)
}
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | 本例では、\ ``@Component``\ アノテーションを使用して、component-scanの対象としている。
| 前述した、\ ``org.springframework.web.servlet.view.BeanNameViewResolver``\ の対象とすることができる。
* - | (2)
- | \ ``AbstractFileDownloadView``\ を継承する。
* - | (3)
- | \ ``getInputStream``\ メソッドを実装する。
| ダウンロード対象の、\ ``InputStream``\ を返却すること。
* - | (4)
- | \ ``addResponseHeaderメソッド``\ を実装する。
| ダウンロードするファイルに合わせた\ ``Content-Disposition``\ や\ ``ContentType``\ を設定する。
* - | (5)
- | \ ``Content-Disposition``\ を設定する。
| 上記例では、\ ``attachment; filename*=UTF-8''サンプル.txt``\ を指定しているため、\ ``サンプル.txt``\ というテキストファイルがダウンロードされる。
| 設定可能な値は\ :url_iana_assignment:`Content Disposition Values and Parameters`\ を参照されたい。
* - | (6)
- | \ ``ContentType``\ を設定する。
| 上記例ではテキストファイルとして扱うため、\ ``text/plain``\ を指定している。
| 設定可能な値は\ :url_iana_assignment:`Media Types`\ を参照されたい。
|
ControllerでのViewの指定
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
| \ ``BeanNameViewResolver``\ により、コントローラで\ ``textFileDownloadView``\ を返却することで、Springのコンテキストで管理されたBeanIDが\ ``textFileDownloadView``\ のViewが使用される。
\ **Javaソース**\
.. code-block:: java
@GetMapping(value = "download")
public String download() {
return "textFileDownloadView"; // (1)
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ``textFileDownloadView``\ をメソッドの戻り値として返却することで、Springのコンテキストで管理された\ ``TextFileDownloadView``\ クラスが実行される。
|
Appendix
--------------------------------------------------------------------------------
動的に生成したファイルのダウンロード
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| \ ``AbstractFileDownloadView``\ は、静的なファイルだけではなく動的に生成したファイルをダウンロードさせる場合にも利用できる。
| ここでは、参考程度にPDFファイルやExcelファイルを動的に生成しダウンロードさせる例を示す。
|
PDFファイルの動的な生成
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
PDFのレンダリングに\ :url_openpdf:`OpenPDF <>`\ を利用する例を紹介する。
|
OpenPDFを使用するための設定
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
\ ``pom.xml``\ に OpenPDFの定義を追加する。
.. code-block:: xml
com.github.librepdf
openpdf
${OPENPDF_VERSION}
|
カスタムViewの実装
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
.. code-block:: java
@Component
public class SamplePdfView extends AbstractFileDownloadView { // (1)
@Inject
private PdfHelper pdfHelper; // (2)
@Override
protected InputStream getInputStream(Map model, HttpServletRequest request)
throws IOException {
return pdfHelper.createPdf(model); // (2)
}
@Override
protected void addResponseHeader(Map model, HttpServletRequest request,
HttpServletResponse response) {
String encodedFileName = URLEncoder.encode("サンプル.pdf", StandardCharsets.UTF_8);
String contentDisposition =
String.format("attachment; filename*=UTF-8''%s", encodedFileName);
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", contentDisposition); // (3)
response.setContentType("application/pdf"); // (4)
}
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ``AbstractFileDownloadView``\ を継承したカスタムViewクラスを実装する。
* - | (2)
- | PDFファイルを生成するためのヘルパークラスを利用する。
| ヘルパークラスの実装例は後述する。
* - | (3)
- | \ ``Content-Disposition``\ を設定する。
| 上記例では、\ ``attachment; filename*=UTF-8''サンプル.pdf``\ を指定しているため、\ ``サンプル.pdf``\ というPDFファイルがダウンロードされる。
* - | (4)
- | \ ``ContentType``\ を設定する。
| PDFファイルとして扱うため、\ ``application/pdf``\ を指定している。
|
ヘルパークラスの実装
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
| Viewに処理を書くのではなく、ヘルパークラスで処理を実装する。
| ヘルパークラスを作成することで再利用性を高めることができる。
| 下記サンプルでは、\ ``model``\ に設定された\ ``serverTime``\ をPDFに出力する単純な例となる。
| 本サンプルで紹介していない機能を使用したい場合は、\ :url_openpdf:`OpenPDF <>`\ のサンプルやチュートリアルを参考に実装されたい。
.. code-block:: java
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import org.springframework.stereotype.Component;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfWriter;
@Component
public class PdfHelper {
public InputStream createServerTimePdf(Map model) throws IOException {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
try (Document document = new Document(PageSize.A4)) { // (1)
PdfWriter.getInstance(document, outputStream); // (2)
document.open();
BaseFont bf = BaseFont.createFont("HeiseiKakuGo-W5", "UniJIS-UCS2-H", false);
Font titleFont = new Font(bf, 18);
Paragraph paragraph = new Paragraph(new Phrase("サンプル PDF", titleFont));
paragraph.setAlignment(Element.ALIGN_CENTER);
document.add(paragraph); // (3)
String serverTime = model.get("serverTime") != null ? model.get("serverTime").toString()
: "Server Time not available";
document.add(new Paragraph("Server Time: " + serverTime)); // (3)
} catch (DocumentException e) {
throw new IOException("Failed to create PDF document", e);
}
return new ByteArrayInputStream(outputStream.toByteArray());
} catch (Exception e) {
throw new IOException("Failed to create PDF document", e);
}
}
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ``Document``\ を生成する。
| 上記例ではA4サイズのPDFを生成している。
* - | (2)
- | \ ``PdfWriter``\ のインスタンスを生成し、DocumentとOutputStreamを関連付ける。
* - | (3)
- | PDFドキュメントに\ ``Paragraph``\ でテキストを追加する。
|
ControllerでのViewの指定
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
| \ ``BeanNameViewResolver``\ により、コントローラで\ ``samplePdfView``\ を返却することで、Springのコンテキストで管理されたBeanIDが\ ``samplePdfView``\ のViewが使用される。
\ **Javaソース**\
.. code-block:: java
@GetMapping(value = "sample", params= "pdf")
public String samplePdf(Model model) {
model.addAttribute("serverTime", LocalDateTime.now());
return "samplePdfView"; // (1)
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ``samplePdfView``\ をメソッドの戻り値として返却することで、Springのコンテキストで管理された\ ``SamplePdfView``\ クラスが実行される。
上記の手順を実行した後、以下に示すようなPDFが生成されダウンロードすることができる。
.. figure:: ./images_FileDownload/file-download-pdf.png
:alt: FILEDOWNLOAD PDF
:width: 60%
:align: center
\ **Picture - FileDownload PDF**\
|
.. _filedownload_excel:
Excelファイルの動的な生成
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Excelのレンダリングに\ :url_apache_poi:`Apache POI >`\ を利用する例を紹介する。
|
Apache POIを使用するための設定
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
\ ``pom.xml``\ に Apache POIの定義を追加する。
.. code-block:: xml
org.apache.poi
poi-ooxml
${POI_VERSION}
.. note::
Apache POI 5.1.0 以降のバージョンでは\ :url_log4j:`Apache Log4j v2 >`\ を依存関係に含んでおり、POIがログ実装としてLog4j 2を直接使用するようになった。
Apache POIで出力されるログを\ |framework_name|\ で設定している\ :url_slf4j:`SLF4J >`\ でログを出力するためには、\ ``log4j-to-slf4j``\ を依存関係に含む必要がある。
.. code-block:: xml
org.apache.logging.log4j
log4j-to-slf4j
なお、Log4j 2を依存関係に含んでいる場合、Apache Commons Logging 1.3のログ実装呼び出し順の関係でSLF4JではなくLog4j 2が優先される可能性があるが、blankプロジェクトから生成されたプロジェクトでは\ ``commons-logging.properties``\ でSLF4Jを優先的に呼び出すように設定しているため、Log4j 2が優先されることはない。
.. warning::
SLF4J adapter (log4j-to-slf4j) とSLF4J bridge (log4j-slf4j-impl) を一緒に使用すると、SLF4Jと Log4j 2の間でイベントが際限なくルーティングされてしまうため注意すること。
詳しくは、\ :url_log4j_adaptor:`Log4j 2 to SLF4J Adapter <>`\ を参照されたい。
|
カスタムViewの実装
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
.. code-block:: java
@Component
public class SampleExcelView extends AbstractFileDownloadView { // (1)
@Autowired
private ExcelHelper excelHelper; // (2)
@Override
protected InputStream getInputStream(Map model, HttpServletRequest request)
throws IOException {
return excelHelper.createServerTimeExcel(model); // (2)
}
@Override
protected void addResponseHeader(Map model, HttpServletRequest request,
HttpServletResponse response) {
String encodedFileName = URLEncoder.encode("サンプル.xlsx", StandardCharsets.UTF_8);
String contentDisposition =
String.format("attachment; filename*=UTF-8''%s", encodedFileName);
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", contentDisposition); // (3)
response.setContentType(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); // (4)
}
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ``AbstractFileDownloadView``\ を継承したカスタムViewクラスを実装する。
* - | (2)
- | EXCELファイルを生成するためのヘルパークラスを利用する。
| ヘルパークラスの実装例は後述する。
* - | (3)
- | \ ``Content-Disposition``\ を設定する。
| 上記例では、\ ``attachment; filename*=UTF-8''サンプル.xlsx``\ を指定しているため、\ ``サンプル.xlsx``\ というEXCELファイルがダウンロードされる。
* - | (4)
- | \ ``ContentType``\ を設定する。
| XLSXフォーマットのEXCELファイルとして扱うため、\ ``application/vnd.openxmlformats-officedocument.spreadsheetml.sheet``\ を指定している。
|
ヘルパークラスの実装
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
| Viewに処理を書くのではなく、ヘルパークラスで処理を実装する。
| ヘルパークラスを作成することで再利用性を高めることができる。
| 下記サンプルでは、\ ``model``\ に設定された\ ``serverTime``\ をEXCELに出力する単純な例となる。
| 本サンプルで紹介していない機能を使用したい場合は、\ :url_apache_poi:`Apache POI >`\ のHow to useを参考に実装されたい。
.. code-block:: java
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Component;
@Component
public class ExcelHelper {
public InputStream createServerTimeExcel(Map model) throws IOException {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Workbook workbook = new XSSFWorkbook()) { // (1)
Sheet sheet = workbook.createSheet("サンプル"); // (2)
sheet.setDefaultColumnWidth(12);
Cell titleCell = getCell(sheet, 0, 0);
titleCell.setCellValue("サンプル Excel"); // (3)
String serverTime = model.get("serverTime") != null ? model.get("serverTime").toString()
: "Server Time not available";
Cell timeCell = getCell(sheet, 2, 0);
timeCell.setCellValue("Server Time: " + serverTime); // (4)
workbook.write(outputStream); // (5)
return new ByteArrayInputStream(outputStream.toByteArray());
} catch (Exception e) {
throw new IOException("Failed to create Excel document", e);
}
}
private Cell getCell(Sheet sheet, int rowNumber, int cellNumber) {
Row row = sheet.getRow(rowNumber);
if (row == null) {
row = sheet.createRow(rowNumber);
}
return row.createCell(cellNumber);
}
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ``Workbook``\ を生成する。
| 上記例ではXLSXフォーマットのEXCELファイルを生成している。
* - | (2)
- | \ ``サンプル``\ シートの作成。
* - | (3)
- | A1セルの値として\ ``サンプル Excel``\ を設定。
* - | (4)
- | A3セルの値として\ ``Server Time: {serverTime}``\ を設定。
* - | (5)
- | \ ``Workbook``\ の内容を\ ``OutputStream``\ に書き込む。
|
ControllerでのViewの指定
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
| \ ``BeanNameViewResolver``\ により、コントローラで\ ``sampleExcelView``\ を返却することで、Springのコンテキストで管理されたBeanIDが\ ``sampleExcelView``\ のViewが使用される。
\ **Javaソース**\
.. code-block:: java
@GetMapping(value = "sample", params= "excel")
public String sampleExcel(Model model) {
model.addAttribute("serverTime", LocalDateTime.now());
return "sampleExcelView"; // (1)
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ``sampleExcelView``\ をメソッドの戻り値として返却することで、Springのコンテキストで管理された\ ``SampleExcelView``\ クラスが実行される。
上記の手順を実行した後、以下に示すようなEXCELが生成されダウンロードすることができる。
.. figure:: ./images_FileDownload/file-download-excel.png
:alt: FILEDOWNLOAD EXCEL
:width: 60%
:align: center
\ **Picture - FileDownload EXCEL**\
|
.. raw:: latex
\newpage