ファイルダウンロード ================================================================================ .. only:: html .. contents:: 目次 :depth: 3 :local: | Overview -------------------------------------------------------------------------------- | 本節では、Springでクライアントにサーバからファイルをダウンロードする機能について説明する。 | Spring MVCのViewで、ファイルのレンダリングを行うことを推奨する。 .. note:: コントローラクラスで、ファイルレンダリングのロジックを持たせることは推奨しない。 理由としては、コントローラの役割から逸脱するためである。 また、コントローラから分離することで、Viewの入れ替えが、容易にできる。 ファイルのダウンロード処理の概要を、以下に示す。 #. DispatchServletは、コントローラへファイルダウンロードのリクエストを送信する。 #. コントローラは、ファイル表示の情報を取得する。 #. コントローラは、Viewを選択する。 #. ファイルレンダリングは、Viewで行われる。 | SpringベースのWebアプリケーションで、ファイルをレンダリングするため、本ガイドラインでは、カスタムビューを実装することを推奨する。 | Spring フレームワークでは、カスタムビューの実装に\ ``org.springframework.web.servlet.View``\ インタフェースを提供している。 | | \ **PDFファイルの場合**\ | Springから提供されている\ ``org.springframework.web.servlet.view.document.AbstractPdfView``\ クラスは、modelの情報を用いてPDFファイルをレンダリングするときに、サブクラスとして利用するクラスである。 | | \ **Excelファイルの場合**\ | Springから提供されている\ ``org.springframework.web.servlet.view.document.AbstractXlsxView``\ クラスは、modelの情報を用いてExcelファイルをレンダリングするときに、サブクラスとして利用するクラスである。 | | Spring では上記以外にも、いろいろなViewの実装を提供している。 | Viewの技術詳細は、\ `Spring Framework Documentation -View Technologies- `_\ を参照されたい。 | 共通ライブラリから提供している、\ ``org.terasoluna.gfw.web.download.AbstractFileDownloadView``\ は、任意のファイルをダウンロードするために使用する抽象クラスである。 | PDFやExcel形式以外のファイルをレンダリングする際に、本クラスをサブクラスに定義する。 .. tip:: ファイルダウンロード機能を提供する際には、ディレクトリトラバーサル攻撃への対策が必要な場合がある。 ディレクトリトラバーサル攻撃については、\ :ref:`file-upload_security_related_warning_points_directory_traversal` \ を参照すること。 | How to use -------------------------------------------------------------------------------- PDFファイルのダウンロード ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | PDFファイルのレンダリングには、Springから提供されている、\ ``org.springframework.web.servlet.view.document.AbstractPdfView``\ を継承したクラスを作成する必要がある。 | コントローラでPDFダウンロードを実装するための手順は、以下で説明する。 | カスタムViewの実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ **AbstractPdfViewを継承したクラスの実装例**\ .. code-block:: java @Component // (1) public class SamplePdfView extends AbstractPdfView { // (2) @Override protected void buildPdfDocument(Map model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response) throws Exception { // (3) document.add(new Paragraph(model.get("serverTime").toString())); } } .. 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) - | \ ``AbstractPdfView``\ を継承する。 * - | (3) - | \ ``buildPdfDocument``\ メソッドを実装する。 | \ ``AbstractPdfView``\ は、PDFのレンダリングに、\ `OpenPDF `_\ を利用している。 | そのため、Mavenのpom.xmlに OpenPDFの定義を追加する必要がある。 .. code-block:: xml com.github.librepdf openpdf .. note:: 上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。 | .. _viewresolver-label: ViewResolverの定義 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``org.springframework.web.servlet.view.BeanNameViewResolver``\ とは、Springのコンテキストで管理されたbean名を用いて実行するViewを選択するクラスである。 \ ``BeanNameViewResolver``\ を使用する際は、通常使用する、 * JSP用の\ ``ViewResolver``\ (\ ``InternalResourceViewResolver``\ ) より先に\ ``BeanNameViewResolver``\ が実行されるように定義する事を推奨する。 .. note:: Spring Frameworkはさまざまな\ ``ViewResolver``\ を提供しており、複数の\ ``ViewResolver``\ をチェーンすることができる。 そのため、特定の状況では、意図しないViewが選択されてしまうことがある。 この動作は、\ ````\ 要素の子要素に、優先したい\ ``ViewResolver``\ を上から順に定義する事で防ぐことができる。 | .. tabs:: .. group-tab:: Java Config .. tabs:: .. group-tab:: JSP \ **bean定義ファイル**\ .. code-block:: java @EnableAspectJAutoProxy @EnableWebMvc @Configuration public class SpringMvcConfig implements WebMvcConfigurer { // omitted @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.beanName(); // (1)(2) 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``\ を定義する。 * - | (2) - | \ ``ViewResolverRegistry#beanName``\ を先に呼び出し、通常使用する\ ``ViewResolver``\ (JSP用の\ ``ViewResolver``\ )より優先度を高くする。 .. group-tab:: Thymeleaf .. code-block:: java @EnableAspectJAutoProxy @EnableWebMvc @Configuration public class SpringMvcConfig implements WebMvcConfigurer { // omitted @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.beanName(); // (1)(2) registry.viewResolver(thymeleafViewResolver()); } @Bean public ThymeleafViewResolver thymeleafViewResolver( SpringTemplateEngine templateEngine) { // omitted } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ViewResolverRegistry#beanName``\ を呼び出し、\ ``BeanNameViewResolver``\ を定義する。 * - | (2) - | \ ``ViewResolverRegistry#beanName``\ を先に呼び出し、通常使用する\ ``ViewResolver``\ (Thymeleaf用の\ ``ViewResolver``\ )より優先度を高くする。 .. group-tab:: XML Config .. tabs:: .. group-tab:: JSP \ **bean定義ファイル**\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ````\ 要素を使用して、\ ``BeanNameViewResolver``\ を定義する。 * - | (2) - | \ ````\ 要素を先頭に定義し、通常使用する\ ``ViewResolver``\ (JSP用の\ ``ViewResolver``\ )より優先度を高くする。 .. group-tab:: Thymeleaf .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ````\ 要素を使用して、\ ``BeanNameViewResolver``\ を定義する。 * - | (2) - | \ ````\ 要素を先頭に定義し、通常使用する\ ``ViewResolver``\ (Thymeleaf用の\ ``ViewResolver``\ )より優先度を高くする。 | コントローラでのViewの指定 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``BeanNameViewResolver``\ により、コントローラで"samplePdfView"を返却することで、Springのコンテキストで管理されたBeanIDにより、"samplePdfView"であるViewが使用される。 \ **Javaソースコード**\ .. code-block:: java @GetMapping(value = "home", params= "pdf") public String homePdf(Model model) { model.addAttribute("serverTime", new Date()); 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**\ | .. _excelfiledownload-label: Excelファイルのダウンロード ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | EXCELファイルのレンダリングには、Springから提供されている、\ ``org.springframework.web.servlet.view.document.AbstractXlsxView``\ を継承したクラスを作成する必要がある。 | コントローラでEXCELファイルをダウンロードさせるための実装手順は、以下で説明する。 | カスタムViewの実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ **AbstractXlsxViewを継承したクラスの実装例**\ .. code-block:: java @Component // (1) public class SampleExcelView extends AbstractXlsxView { // (2) @Override protected void buildExcelDocument(Map 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); } } .. 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) - | \ ``AbstractXlsxView``\ を継承する。 * - | (3) - | \ ``buildExcelDocument``\ メソッドを実装する。 | \ ``AbstractXlsxView``\ は、EXCELのレンダリングに、\ `Apache POI `_\ を利用している。 | そのため、Mavenのpom.xmlに POIの定義を追加する必要がある。 .. code-block:: xml org.apache.poi poi-ooxml .. note:: 上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。 .. note:: xlsファイル形式をサポートしたい場合は \ ``AbstractXlsView``\ を使用されたい。 詳細は、\ `AbstractXlsViewのJavaDoc `_\ を参照されたい。 .. 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へブリッジされるようになる。 .. code-block:: xml org.apache.logging.log4j log4j-to-slf4j 上記設定例は、依存ライブラリのバージョンを親プロジェクトである 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 `_\ を参照されたい。 | ViewResolverの定義 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 設定は、PDFファイルをレンダリングする場合と同様である。詳しくは、\ :ref:`viewresolver-label`\ を参照されたい。 | コントローラでのViewの指定 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``BeanNameViewResolver``\ により、コントローラで"sampleExcelView"を返却することで、Springのコンテキストで管理されたBeanIDにより、”sampleExcelView”であるViewが使用される。 \ **Javaソース**\ .. code-block:: java @GetMapping(value = "home", params= "excel") public String homeExcel(Model model) { model.addAttribute("serverTime", new Date()); 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**\ | 任意のファイルのダウンロード ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | 前述した、PDFやEXCELファイル以外のファイルのダウンロードを行う場合、共通ライブラリが提供している、\ ``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 { @Override protected InputStream getInputStream(Map model, HttpServletRequest request) throws IOException { // (3) Resource resource = new ClassPathResource("abc.txt"); return resource.getInputStream(); } @Override protected void addResponseHeader(Map model, HttpServletRequest request, HttpServletResponse response) { // (4) response.setHeader("Content-Disposition", "attachment; filename=abc.txt"); response.setContentType("text/plain"); } } .. 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を設定する。 | ViewResolverの定義 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 設定は、PDFファイルをレンダリングする場合と同様である。詳しくは、\ :ref:`viewresolver-label`\ を参照されたい。 | コントローラでの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``\ クラスが実行される。 .. tip:: 前述してきたように、SpringはModelの情報をいろいろなViewにレンダリングすることができる。Springでは、複数のレンダリングエンジンをサポートしており、さまざまなViewを返却することが可能である。 詳細は、Spring の公式ドキュメント\ `Spring Framework Documentation -View Technologies- `_\ を参照されたい。 .. raw:: latex \newpage