--- title: SDKを使わずにSpringのRestTemplate/RestClientを使ってS3 APIにアクセスする tags: ["Java", "S3", "Spring Boot"] categories: ["Programming", "Java", "am", "ik", "s3"] date: 2024-07-08T04:56:20Z updated: 2024-07-09T00:52:16Z --- JavaでS3にアクセスする場合には、 * [AWS SDK for Java 2.0](https://github.com/aws/aws-sdk-java-v2/) * [MinIO Java SDK](https://github.com/minio/minio-java) が候補に挙がります。メンテナンスされており、機能が豊富である一方、これらのライブラリは依存ライブラリが非常に多いです。 AWS SDK for Java 2.0の場合、 ``` \- software.amazon.awssdk:s3:jar:2.26.16:compile +- software.amazon.awssdk:aws-xml-protocol:jar:2.26.16:compile | \- software.amazon.awssdk:aws-query-protocol:jar:2.26.16:compile +- software.amazon.awssdk:protocol-core:jar:2.26.16:compile +- software.amazon.awssdk:arns:jar:2.26.16:compile +- software.amazon.awssdk:profiles:jar:2.26.16:compile +- software.amazon.awssdk:crt-core:jar:2.26.16:compile +- software.amazon.awssdk:http-auth:jar:2.26.16:compile +- software.amazon.awssdk:identity-spi:jar:2.26.16:compile +- software.amazon.awssdk:http-auth-spi:jar:2.26.16:compile | \- org.reactivestreams:reactive-streams:jar:1.0.4:compile +- software.amazon.awssdk:http-auth-aws:jar:2.26.16:compile +- software.amazon.awssdk:checksums:jar:2.26.16:compile +- software.amazon.awssdk:checksums-spi:jar:2.26.16:compile +- software.amazon.awssdk:retries-spi:jar:2.26.16:compile +- software.amazon.awssdk:sdk-core:jar:2.26.16:compile | +- software.amazon.awssdk:retries:jar:2.26.16:compile | \- org.slf4j:slf4j-api:jar:2.0.13:compile +- software.amazon.awssdk:auth:jar:2.26.16:compile | \- software.amazon.eventstream:eventstream:jar:1.0.1:compile +- software.amazon.awssdk:http-client-spi:jar:2.26.16:compile +- software.amazon.awssdk:regions:jar:2.26.16:compile +- software.amazon.awssdk:annotations:jar:2.26.16:compile +- software.amazon.awssdk:utils:jar:2.26.16:compile +- software.amazon.awssdk:aws-core:jar:2.26.16:compile +- software.amazon.awssdk:metrics-spi:jar:2.26.16:compile +- software.amazon.awssdk:json-utils:jar:2.26.16:compile | \- software.amazon.awssdk:third-party-jackson-core:jar:2.26.16:compile +- software.amazon.awssdk:endpoints-spi:jar:2.26.16:compile +- software.amazon.awssdk:apache-client:jar:2.26.16:runtime | +- org.apache.httpcomponents:httpclient:jar:4.5.13:runtime | | \- commons-logging:commons-logging:jar:1.2:runtime | +- org.apache.httpcomponents:httpcore:jar:4.4.16:runtime | \- commons-codec:commons-codec:jar:1.16.1:runtime \- software.amazon.awssdk:netty-nio-client:jar:2.26.16:runtime +- io.netty:netty-codec-http:jar:4.1.111.Final:runtime +- io.netty:netty-codec-http2:jar:4.1.111.Final:runtime +- io.netty:netty-codec:jar:4.1.111.Final:runtime +- io.netty:netty-transport:jar:4.1.111.Final:runtime +- io.netty:netty-common:jar:4.1.111.Final:runtime +- io.netty:netty-buffer:jar:4.1.111.Final:runtime +- io.netty:netty-handler:jar:4.1.111.Final:runtime | \- io.netty:netty-transport-native-unix-common:jar:4.1.111.Final:runtime +- io.netty:netty-transport-classes-epoll:jar:4.1.111.Final:runtime \- io.netty:netty-resolver:jar:4.1.111.Final:runtime ``` MinIO Java SDKの場合、 ``` \- io.minio:minio:jar:8.5.11:compile +- com.carrotsearch.thirdparty:simple-xml-safe:jar:2.7.1:compile +- com.google.guava:guava:jar:33.0.0-jre:compile | +- com.google.guava:failureaccess:jar:1.0.2:compile | +- com.google.guava:listenablefuture:jar:9999.0-empty-to-avoid-conflict-with-guava:compile | +- com.google.code.findbugs:jsr305:jar:3.0.2:compile | +- org.checkerframework:checker-qual:jar:3.41.0:compile | +- com.google.errorprone:error_prone_annotations:jar:2.23.0:compile | \- com.google.j2objc:j2objc-annotations:jar:2.8:compile +- com.squareup.okhttp3:okhttp:jar:4.12.0:compile | +- com.squareup.okio:okio:jar:3.6.0:compile | | \- com.squareup.okio:okio-jvm:jar:3.6.0:compile | | \- org.jetbrains.kotlin:kotlin-stdlib-common:jar:1.9.24:compile | \- org.jetbrains.kotlin:kotlin-stdlib-jdk8:jar:1.9.24:compile | +- org.jetbrains.kotlin:kotlin-stdlib:jar:1.9.24:compile | | \- org.jetbrains:annotations:jar:13.0:compile | \- org.jetbrains.kotlin:kotlin-stdlib-jdk7:jar:1.9.24:compile +- com.fasterxml.jackson.core:jackson-annotations:jar:2.17.1:compile +- com.fasterxml.jackson.core:jackson-core:jar:2.17.1:compile +- com.fasterxml.jackson.core:jackson-databind:jar:2.17.1:compile +- org.bouncycastle:bcprov-jdk18on:jar:1.78:compile +- org.apache.commons:commons-compress:jar:1.26.0:compile | +- commons-io:commons-io:jar:2.15.1:compile | \- org.apache.commons:commons-lang3:jar:3.14.0:compile +- commons-codec:commons-codec:jar:1.16.1:compile \- org.xerial.snappy:snappy-java:jar:1.1.10.5:compile ``` となります。 > [!NOTE] > EoLが近づいている[AWS SDK for Java](https://github.com/aws/aws-sdk-java/)の場合は、幾分ましな依存関係です。 > > ``` > \- com.amazonaws:aws-java-sdk-s3:jar:1.12.757:compile > +- com.amazonaws:aws-java-sdk-kms:jar:1.12.757:compile > +- com.amazonaws:aws-java-sdk-core:jar:1.12.757:compile > | +- commons-logging:commons-logging:jar:1.1.3:compile > | +- commons-codec:commons-codec:jar:1.16.1:compile > | +- org.apache.httpcomponents:httpclient:jar:4.5.13:compile > | | \- org.apache.httpcomponents:httpcore:jar:4.4.16:compile > | +- com.fasterxml.jackson.core:jackson-databind:jar:2.17.1:compile > | | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.17.1:compile > | | \- com.fasterxml.jackson.core:jackson-core:jar:2.17.1:compile > | +- com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:jar:2.17.1:compile > | \- joda-time:joda-time:jar:2.12.7:compile > \- com.amazonaws:jmespath-java:jar:1.12.757:compile > ``` 他のAWSのサービスも使用している場合は、AWS SDK for Java 2.0でも良いかもしれませんが、 例えば、S3互換なObject Storageにシンプルなput/getくらいしかしていない場合は、これらのライブラリはtoo muchです。 また、AWS SDKの場合はApache HTTP Client or Netty、MinIO SDKの場合は(Kotlinで実装された)OkHttpとHTTPクライアントがそれぞれ組み込まれています。 Spring Bootでアプリを作成する場合、通常`RestTemplate`や`RestClient`を使用します。 SDK側でHTTPクライアントが別途使われている場合、アプリ側で設定したObservability, Logging, RetryなどのInterceptorを共通的に利用できなくなり不便です。 アクセスキーとシークレットキーによる認証でput/getくらいしかしないようなシンプルなユースケースでは、単純にS3用にHTTP Headersを作成し、XMLのマーシャル・アンマーシャルだけ行えれば十分です。 それはSDKを使わずとも`RestTemplate`や`RestClient`だけで事足ります。 というわけで[simple-s3-client](https://github.com/making/simple-s3-client)というライブラリを作りました。 "s3-client"という名前がついていますが、HTTP Headersを作るユーティリティとよく使うAPIのデータクラスが含まれているだけです。 以下のdependencyを追加すれば使えます。 ```xml am.ik.s3 simple-s3-client 0.2.2 ``` 使い方は次の通りです。 ```java import static am.ik.s3.S3RequestBuilder.s3Request; URI endpoint = URI.create("https://..."); String region = "..."; String accessKeyId = "..."; String secretAccessKey = "..."; String bucket = "..."; ``` ObjectのPUTは次のように行えます。 ```java // Put an object String body = "Hello World!"; S3Request putObjectRequest = s3Request().endpoint(endpoint) .region(region) .accessKeyId(accessKeyId) .secretAccessKey(secretAccessKey) .method(HttpMethod.PUT) .path(b -> b.bucket(bucket).key("hello.txt")) .content(S3Content.of(body, MediaType.TEXT_PLAIN)) .build(); ``` `RestTemplate`の場合 ```java restTemplate.exchange(putObjectRequest.toEntityBuilder().body(body), Void.class); ``` `RestClient`の場合 ```java restClient.put() .uri(putObjectRequest.uri()) .headers(putObjectRequest.headers()) .body(body) .retrieve() .toBodilessEntity(); ``` > [!NOTE] > マルチパートは未対応です。 ObjectのGETは次のように行えます。 ```java // Get an object S3Request getObjectRequest = s3Request().endpoint(endpoint) .region(region) .accessKeyId(accessKeyId) .secretAccessKey(secretAccessKey) .method(HttpMethod.GET) .path(b -> b.bucket(bucket).key("hello.txt")) .build(); ``` `RestTemplate`の場合 ```java String response = restTemplate.exchange(getObjectRequest.toEntityBuilder().build(), String.class).getBody(); System.out.println("Response: " + response); // Response: Hello World! ``` `RestClient`の場合 ```java String response = restClient.get() .uri(getObjectRequest.uri()) .headers(getObjectRequest.headers()) .retrieve() .body(String.class); System.out.println("Response: " + response); // Response: Hello World! ``` その他の使い方は[README](https://github.com/making/simple-s3-client)を確認してください。 依存ライブラリは次の通りで、Spring BootでWebアプリを作るのに必須な`spring-web`とJacksonでXMLを扱うための`jackson-dataformat-xml`だけなので、 一般的なSpring Bootアプリ観点では`jackson-dataformat-xml`が足されただけで済みます。 ``` \- am.ik.s3:simple-s3-client:jar:0.2.2:compile +- org.springframework:spring-web:jar:6.1.10:compile | +- org.springframework:spring-beans:jar:6.1.10:compile | +- org.springframework:spring-core:jar:6.1.10:compile | | \- org.springframework:spring-jcl:jar:6.1.10:compile | \- io.micrometer:micrometer-observation:jar:1.13.1:compile | \- io.micrometer:micrometer-commons:jar:1.13.1:compile +- com.fasterxml.jackson.dataformat:jackson-dataformat-xml:jar:2.17.1:compile | +- com.fasterxml.jackson.core:jackson-core:jar:2.17.1:compile | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.17.1:compile | +- com.fasterxml.jackson.core:jackson-databind:jar:2.17.1:compile | \- org.codehaus.woodstox:stax2-api:jar:4.2.2:compile \- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.17.1:compile ```