SpringでS3にあるいくつかのファイルをZipに固めてダウンロードする
こんにちは。サービスGの金谷です。
今回はSpringプロジェクトでS3に置いてあるファイルをZipにまとめてダウンロードする方法を紹介します。
はじめに
今回紹介する方法はダウンロード対象となるS3オブジェクトに逐一アクセスしにいくような形なので、
ファイル数やサイズが大きくなるとパフォーマンス面でネックになるかもしれないのと、
S3はデータの取り出し操作にも多少の料金が発生するので、
可能であれば事前にS3側でファイルをZipにまとめてからダウンロードするのが一番良いと思います。
あくまでいくつかある方法の中の一つとして考えていただければと思います。
Springのプロジェクトを用意する
今回は以下の設定で作成しました。
設定 | 値 |
---|---|
Project | Gradle Project |
Language | Java |
Spring Boot | 2.4.3 |
Project Metadata | 特に変更なし |
Packaging | Jar |
Java | 8 |
Dependencies | Spring Boot DevTools, Spring Web |
設定できたらGENERATEを押してダウンロードします。
AWS SDKの依存関係を追加する
プロジェクトを取り込んだらAWS SDKの依存関係をbuild.gradleに追記します。
今回はS3のみ使用するのでaws-java-sdk-s3を追加します。
また、今回はversion1.11.969を使います。
dependencies { // ・・・略 implementation group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '1.11.969' }
S3にサンプルデータを配置
適当にバケットを作成し、ファイルを配置します。
今回の例では以下のデータを用意したことにします(実際は存在しません。各環境で適宜読み替えてください。)
バケット名: my-sample-bucket
オブジェクト1:directory1/sample.pdf
オブジェクト2:directory2/sample.pdf
Controllerを作成する
まずは適当にControllerクラスを追加してみます。
@RestController public class DemoController { @GetMapping("/") public String index() { return "Hello Java"; } }
localhost:8080でアクセスしてHello Javaが表示できればOKです。
次にS3のファイルを取得するエンドポイントを追加していきます。
S3から1つのファイルをダウンロードする
以下のようにダウンロード処理をControllerに追記します。
@RestController public class DemoController { @GetMapping("/download") public ResponseEntity<byte[]> download() throws IOException { // Profileやリージョンは適宜置き換える AmazonS3 s3 = AmazonS3ClientBuilder.standard() .withCredentials(new ProfileCredentialsProvider("PROFILE")) .withRegion(Regions.AP_NORTHEAST_1) .build(); // バケット名とキーは適宜置き換える S3Object o = s3.getObject("my-sample-bucket", "directory1/sample.pdf"); byte[] data = IOUtils.toByteArray(o.getObjectContent()); HttpHeaders respHeaders = new HttpHeaders(); respHeaders.setContentType(MediaType.APPLICATION_PDF); respHeaders.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=sample.pdf"); return new ResponseEntity<byte[]>(data, respHeaders, HttpStatus.OK); } }
S3クライアントの生成方法は色々あるので、各環境に合った方法で実行してください。今回はProfileから生成しています。
公式ドキュメントのサンプルでは使われていませんでしたが、S3オブジェクトからJavaのbyte配列への変換にはcom.amazonaws.util.IOUtils
を使用すると比較的楽に変換できました。
あとはいつも通りHeaderを設定してResponseEntityを返します。
複数のファイルをZipに固めてダウンロード
Java SDKには複数のS3のファイルをダウンロードするような機能はないので、自前で頑張ります。
最終的に以下のような形で動きました。
@RestController public class DemoController { @GetMapping("/zip") public ResponseEntity<byte[]> zip() throws IOException { // キーの配列 String[] objectKeys = {"directory1/sample.pdf", "directory2/sample.pdf"}; // クライアントの作成 AmazonS3 s3 = AmazonS3ClientBuilder.standard() .withCredentials(new ProfileCredentialsProvider("PROFILE")) .withRegion(Regions.AP_NORTHEAST_1) .build(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); try(ZipOutputStream zos = new ZipOutputStream(baos)) { for(String objectKey : objectKeys) { // S3からデータ取得 S3Object o = s3.getObject("my-sample-bucket", objectKey); byte[] data = IOUtils.toByteArray(o.getObjectContent()); ZipEntry zip = new ZipEntry(objectKey); zos.putNextEntry(zip); zos.write(data); zos.closeEntry(); } } byte[] responseData = baos.toByteArray(); HttpHeaders respHeaders = new HttpHeaders(); respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); respHeaders.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=data.zip"); return new ResponseEntity<byte[]>(responseData, respHeaders, HttpStatus.OK); } }
キーの数だけループし、オブジェクトを取得しつつZipにまとめています。
署名付きURLなどを使う場合もS3Objectを取得する部分を変更すれば使用できると思います。
JavaでのZip圧縮周りのことは前回書いた記事にまとめていますのでそちらをご参照ください。