※当サイトの記事には、広告・プロモーションが含まれます。

Javaで複数のCSVファイルを一度にダウンロードするには

www.itmedia.co.jp

⇧ 異常系の実装不備ってことですかね?

開発時に高負荷が起きることは想定してると思うから、要件定義の非機能要件、基本設計あたりは正しかったとして、詳細設計の段階で不備があったってことなんかな?

何が悪かったのか情報共有してくれると後進のためになるとは思うんだけど...

Javaで複数のCSVファイルを一度にダウンロードするには

ネットで検索してみたけど、

atmarkit.itmedia.co.jp

HTTPというのは、一つのリクエストに対して一つのレスポンスを返すプロトコルなので、
複数のファイルを返すことは原理的に不可能です。

対応策としては、
・ファイル数回のリクエストを繰り返すようなscriptをクライアント側で実行させる。
・multipartなレスポンスを返す。(IEが対応してないので、残念ながらほとんど無意味)
・複数ファイルを一個のzipにまとめてダウンロードする。
という辺りでしょうか。

「複数ファイルのダウンロード」(1) Java Solution − @IT

⇧ なるほど、やっぱりzipのような圧縮する方法を使うしかないっぽいのか。

ちなみに、ChatGPTさんに確認してみて得られた回答が、以下。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

@Controller
public class DownloadController {

    @GetMapping("/download")
    public ModelAndView download(HttpServletResponse response) {
        String[] fileNames = {"file1.csv", "file2.csv", "file3.csv"};

        try {
            for (String fileName : fileNames) {
                File file = new File(fileName);
                response.setContentType("text/csv");
                response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
                response.setContentLength((int) file.length());

                FileInputStream in = new FileInputStream(file);
                OutputStream out = response.getOutputStream();

                byte[] buffer = new byte[4096];
                int length;
                while ((length = in.read(buffer)) > 0) {
                    out.write(buffer, 0, length);
                }

                in.close();
                out.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null; // ダウンロード処理が完了したため、ModelAndViewを返さない
    }
}    

⇧ 動かしてみたけど、file1.csvだけダウンロードされて、file2.csv、file3.csvについてはダウンロードされないという...

ChatGPTさん自信満々に嘘つくところをどうにかして欲しい...

というわけで、複数ファイルを一度にダウンロードするには、zipのような圧縮方法を使うしか無さそう。

Javaで複数ファイルからzip作成して一度にダウンロードを実装してみる

というわけで、

stackoverflow.com

stackoverflow.com

⇧ 上記サイト様を参考に、複数ファイルをzip化して一度にダウンロードする実装を試してみました。

zip化するCSVファイルについては、あらかじめ配置されているという想定なので、CSVファイルの中身をプログラムで作成するといったことは行っていません。

⇧ ダウンロードするcsvファイルは上記の場所に配置してます。

stackoverflowの情報を参考にChatGTPの回答を修正して、以下のようなソースコードになりました。

package com.controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("downloadController")
public class DownloadController {
	
    @GetMapping("/download")
    public ModelAndView download(HttpServletResponse response) {

    	// zip化したいファイル名
    	String[] fileNames = {"file1.csv", "file2.csv", "file3.csv"};
        // zip化したいファイルが配置されているパス
    	String dirPath = "C:\\Eclipse-2022-03\\workspace03\\spring-mvc-jsp";
        // zip化後のファイル名
    	String zipFileName ="file.zip";

    	// zip化したいファイルを作成
        List<File> fileList = createFiles(fileNames, dirPath);
        
        // zip化
        File zipFile = zip(fileList, zipFileName);

        //  zip化したファイルをダウンロード
		try {
			// File file = new File(dirPath + File.separator + fileName);
			response.setContentType("text/csv");
			response.setHeader("Content-Disposition", "attachment; filename=\"" + zipFile + "\"");
			response.setContentLength((int) zipFile.length());

			FileInputStream in = new FileInputStream(zipFile);
			OutputStream out = response.getOutputStream();

			byte[] buffer = new byte[4096];
			int length;
			while ((length = in.read(buffer)) > 0) {
				out.write(buffer, 0, length);
			}

			in.close();
			// ダウンロード
			out.flush();

		} catch (IOException e) {
			e.printStackTrace();
		}

        return null; // ダウンロード処理が完了したため、ModelAndViewを返さない
    }
    
    /**
     * zip化する
     * @param files ファイルのリスト
     * @param zipFileName zip後のファイル名
     * @return zip化されたファイル
     */
    public static File zip(List<File> files, String zipFileName) {
        File zipfile = new File(zipFileName);
        // Create a buffer for reading the files
        byte[] buf = new byte[1024];
        try {
            // create the ZIP file
            ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipfile));
            // compress the files
            for(int i=0; i<files.size(); i++) {
                FileInputStream in = new FileInputStream(files.get(i).getCanonicalFile());
                // add ZIP entry to output stream
                out.putNextEntry(new ZipEntry(files.get(i).getName()));
                // transfer bytes from the file to the ZIP file
                int len;
                while((len = in.read(buf)) > 0) {
                    out.write(buf, 0, len);
                }
                // complete the entry
                out.closeEntry();
                in.close();
            }
            // complete the ZIP file
            out.finish();
            out.close();
            return zipfile;
        } catch (IOException ex) {
            System.err.println(ex.getMessage());
        }
        return null;
    }
    
    /**
     * zip化する対象のファイルを作成する
     * @param fileNames ファイル名のリスト
     * @param dirPath ファイルの配置パス
     * @return ファイルのリスト
     */
    private static List<File> createFiles(String[] fileNames, String dirPath) {
    	List<File> fileList = new ArrayList<>();
    	for (String fileName : fileNames) {
            fileList.add(new File(dirPath + File.separator + fileName));
    	}
    	return fileList;
    }
}

⇧ で、Tomcatを起動してブラウザで画面を表示して、画面のダウンロードボタンを押下して上記のメソッドのにリクエストを投げると、

⇧ zipファイルがダウンロードされて、展開(解凍)すると、

⇧ 複数ファイルがダウンロードできています。

というわけで、Javaで複数ファイルをダウンロードする場合は、Zip化などで圧縮する感じになるということでしょうかね。

複数ファイルの内容を1つのファイルにまとめる方法でも一度にダウンロードを実現できると思いますが、複数ファイルの状態を維持した形で一度にダウンロードするとなると圧縮するしかない感じなんですかね?

毎度モヤモヤ感が半端ない...

今回はこのへんで。