I am using the S3TransferManager
with S3AsyncClient
in my service code. I am testing this code with localstack docker S3 service. I have the local stack S3 service and created the bucket test
and uploaded one file test.txt
on docker service startup.
S3Config.java
@Configuration
public class S3Configuration {
@Value("${aws.access-key}")
private String accessKey;
@Value("${aws.secret-key}")
private String secretKey;
@Value("${aws.region}")
private String region;
@Value("${aws.endpoint:#{null}}")
private String endpoint;
@Bean
public S3AsyncClient s3AsyncClient(AwsCredentialsProvider awsCredentialsProvider) {
S3CrtAsyncClientBuilder s3CrtAsyncClientBuilder = S3AsyncClient.crtBuilder()
.region(Region.of(region))
.credentialsProvider(awsCredentialsProvider)
.targetThroughputInGbps(20.0)
.minimumPartSizeInBytes(8 * MB);
if (StringUtils.hasText(endpoint)) {
s3CrtAsyncClientBuilder.endpointOverride(URI.create(endpoint));
}
return s3CrtAsyncClientBuilder.build();
}
@Bean
AwsCredentialsProvider awsCredentialsProvider() {
return () -> AwsBasicCredentials.create(accessKey, secretKey);
}
}
S3StorageServiceImpl:
@Service
@Primary
public class S3StorageServiceImpl implements S3StorageService {
public static final String ERROR_MSG_FORMAT = "Error performing S3 client operation";
private static final Logger LOG = LoggerFactory.getLogger(S3StorageServiceImpl.class);
private final S3AsyncClient s3AsyncClient;
public S3StorageServiceImpl(S3AsyncClient s3AsyncClient) {
this.s3AsyncClient = s3AsyncClient;
}
@Override
public byte[] downloadAsByteArray(String bucketName, String keyName) {
try (S3TransferManager transferManager = S3TransferManager.builder().s3Client(s3AsyncClient).build()) {
DownloadRequest < ResponseBytes < GetObjectResponse >> downloadFileRequest =
DownloadRequest.builder()
.getObjectRequest(req -> req.bucket(bucketName).key(keyName))
.responseTransformer(AsyncResponseTransformer.toBytes())
.addTransferListener(LoggingTransferListener.create())
.build();
// Wait for the transfer to complete
return transferManager.download(downloadFileRequest).completionFuture()
.thenApply(CompletedDownload::result)
.thenApply(BytesWrapper::asByteArray)
.join();
} catch (CompletionException e) {
LOG.error(ERROR_MSG_FORMAT, e);
return new byte[0];
}
}
}
docker compose for localstack service:
version: '3.4'
localstack:
container_name: "localstack_main"
image: localstack/localstack
ports:
- "4566-4599:4566-4599"
environment:
- SERVICES=s3
- AWS_DEFAULT_REGION=us-east-1
volumes:
- ./docker/files/test.txt:/home/docker/data/test.txt
- ./docker/files/init-s3-bucket.sh:/docker-entrypoint-initaws.d/init-s3-bucket.sh
Above /docker/files/
is local directory from project. And init-s3-bucket.sh contains the AWS cli code to create the test bucket and upload the test.txt file.
Spring Boot test :
@SpringBootTest(classes = {
S3StorageServiceImpl.class,
S3Configuration.class
})
class S3StorageServiceImplTest {
@Autowired
private S3StorageService s3StorageService;
@Test
void downloadAsByteArray() {
byte[] byteArray = s3StorageService.downloadAsByteArray("test", "test.txt");
assertThat(byteArray).isNotEmpty();
}
}
applicaton-test.yml
has aws.endpoint=http://localhost:4566
so that it can connect localstack container.
When I am running the test getting below error
java.util.concurrent.CompletionException: software.amazon.awssdk.services.s3.model.S3Exception: null (Service: S3, Status Code: 404, Request ID: 98gby23UsHdBcGi4dwT3T4BNjeMa3hyFMS0coOqQMmD1Y3TL4sVu, Extended Request ID: MzRISOwyjmnup7C0E0AFC7D5516C77/JypPGXLh0OVFGcJaaO3KW/hRAqKOpIEEp)
at software.amazon.awssdk.utils.CompletableFutureUtils.errorAsCompletionException(CompletableFutureUtils.java:65)
at software.amazon.awssdk.core.internal.http.pipeline.stages.AsyncExecutionFailureExceptionReportingStage.lambda$execute$0(AsyncExecutionFailureExceptionReportingStage.java:51)
at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934)
at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:911)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2162)
at software.amazon.awssdk.utils.CompletableFutureUtils.lambda$forwardExceptionTo$0(CompletableFutureUtils.java:79)
at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:863)
at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:841)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2162)
at software.amazon.awssdk.core.internal.http.pipeline.stages.AsyncRetryableStage$RetryingExecutor.maybeAttemptExecute(AsyncRetryableStage.java:103)
at software.amazon.awssdk.core.internal.http.pipeline.stages.AsyncRetryableStage$RetryingExecutor.maybeRetryExecute(AsyncRetryableStage.java:184)
at software.amazon.awssdk.core.internal.http.pipeline.stages.AsyncRetryableStage$RetryingExecutor.lambda$attemptExecute$1(AsyncRetryableStage.java:170)
at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:863)
at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:841)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2147)
at software.amazon.awssdk.core.internal.http.pipeline.stages.MakeAsyncHttpRequestStage.lambda$null$0(MakeAsyncHttpRequestStage.java:105)
at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:863)
at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:841)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2147)
at software.amazon.awssdk.core.internal.http.pipeline.stages.MakeAsyncHttpRequestStage.lambda$executeHttpRequest$3(MakeAsyncHttpRequestStage.java:163)
at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:863)
at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:841)
at java.base/java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:482)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: software.amazon.awssdk.services.s3.model.S3Exception: null (Service: S3, Status Code: 404, Request ID: 98gby23UsHdBcGi4dwT3T4BNjeMa3hyFMS0coOqQMmD1Y3TL4sVu, Extended Request ID: MzRISOwyjmnup7C0E0AFC7D5516C77/JypPGXLh0OVFGcJaaO3KW/hRAqKOpIEEp)
at software.amazon.awssdk.services.s3.model.S3Exception$BuilderImpl.build(S3Exception.java:104)
at software.amazon.awssdk.services.s3.model.S3Exception$BuilderImpl.build(S3Exception.java:58)
at software.amazon.awssdk.protocols.query.internal.unmarshall.AwsXmlErrorUnmarshaller.unmarshall(AwsXmlErrorUnmarshaller.java:99)
at software.amazon.awssdk.protocols.query.unmarshall.AwsXmlErrorProtocolUnmarshaller.handle(AwsXmlErrorProtocolUnmarshaller.java:102)
at software.amazon.awssdk.protocols.query.unmarshall.AwsXmlErrorProtocolUnmarshaller.handle(AwsXmlErrorProtocolUnmarshaller.java:82)
at software.amazon.awssdk.core.http.MetricCollectingHttpResponseHandler.lambda$handle$0(MetricCollectingHttpResponseHandler.java:52)
at software.amazon.awssdk.core.internal.util.MetricUtils.measureDurationUnsafe(MetricUtils.java:63)
at software.amazon.awssdk.core.http.MetricCollectingHttpResponseHandler.handle(MetricCollectingHttpResponseHandler.java:52)
at software.amazon.awssdk.core.internal.http.async.AsyncResponseHandler.lambda$prepare$0(AsyncResponseHandler.java:89)
at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2147)
at software.amazon.awssdk.core.internal.http.async.AsyncResponseHandler$BaosSubscriber.onComplete(AsyncResponseHandler.java:132)
at software.amazon.awssdk.utils.async.SimplePublisher.doProcessQueue(SimplePublisher.java:275)
at software.amazon.awssdk.utils.async.SimplePublisher.processEventQueue(SimplePublisher.java:224)
at software.amazon.awssdk.utils.async.SimplePublisher.complete(SimplePublisher.java:157)
at java.base/java.util.concurrent.CompletableFuture.uniRunNow(CompletableFuture.java:819)
at java.base/java.util.concurrent.CompletableFuture.uniRunStage(CompletableFuture.java:803)
at java.base/java.util.concurrent.CompletableFuture.thenRun(CompletableFuture.java:2195)
at software.amazon.awssdk.services.s3.internal.crt.S3CrtResponseHandlerAdapter.onErrorResponseComplete(S3CrtResponseHandlerAdapter.java:135)
at software.amazon.awssdk.services.s3.internal.crt.S3CrtResponseHandlerAdapter.handleError(S3CrtResponseHandlerAdapter.java:124)
at software.amazon.awssdk.services.s3.internal.crt.S3CrtResponseHandlerAdapter.onFinished(S3CrtResponseHandlerAdapter.java:93)
at software.amazon.awssdk.crt.s3.S3MetaRequestResponseHandlerNativeAdapter.onFinished(S3MetaRequestResponseHandlerNativeAdapter.java:24)
java.lang.AssertionError:
Expecting actual not to be empty
I have created repo with miniman reproducible example. Please find it here