使用S3TransferManager结合LocalStack下载时触发S3Exception(404)
解决LocalStack S3下载404问题的方案
核心问题排查与修复步骤
1. 强制S3AsyncClient指向LocalStack并开启路径样式访问
AWS SDK默认会请求真实S3服务,必须手动指定LocalStack的端点;另外LocalStack默认只支持路径样式访问,而新版SDK默认用虚拟主机样式,这是导致404的高频原因。
修改你的S3客户端配置类,补上关键配置:
S3AsyncClient s3AsyncClient = S3AsyncClient.builder() .endpointOverride(URI.create("http://localhost:4566")) // LocalStack S3默认端口 .region(Region.US_EAST_1) // 和LocalStack启动区域保持一致 .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("test", "test"))) .serviceConfiguration(config -> config.pathStyleAccessEnabled(true)) // 必须开启路径样式 .build();
2. 验证LocalStack初始化脚本的正确性
检查Docker Compose挂载的初始化脚本,确保用awslocal(LocalStack专属CLI封装)而非原生aws命令创建桶和上传文件:
# Docker Compose示例配置 services: localstack: image: localstack/localstack:latest ports: - "4566:4566" environment: - SERVICES=s3 - DEFAULT_REGION=us-east-1 volumes: - "./init-scripts:/docker-entrypoint-initaws.d"
初始化脚本init-scripts/s3-init.sh里的命令要写对:
awslocal s3 mb s3://test awslocal s3 cp ./test.txt s3://test/test.txt
3. 给测试加初始化等待逻辑
测试代码可能在LocalStack还没完成桶和文件创建时就执行,导致404。可以在测试类里加个简单的等待逻辑:
@BeforeEach void setup() { // 重试10次,等待LocalStack服务就绪 int retry = 0; while (retry < 10) { try (HttpClient client = HttpClient.newHttpClient()) { var request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:4566/health")) .GET() .build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 200) break; } catch (Exception e) {} try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } retry++; } }
4. 确保区域配置完全一致
以下三处的区域必须完全相同:
- LocalStack启动时的
DEFAULT_REGION环境变量 - S3AsyncClient配置的
region参数 - 初始化脚本创建桶时的区域(如果没指定,会用LocalStack默认区域,要和上面保持一致)
测试代码调整示例
确认下载逻辑里的桶名、对象键和初始化的一致:
TransferManager transferManager = TransferManagerBuilder.standard() .s3Client(s3AsyncClient) .build(); File downloadFile = new File("./downloaded-test.txt"); Download download = transferManager.download("test", "test.txt", downloadFile); download.waitForCompletion(); // 断言文件存在且内容匹配 assertTrue(downloadFile.exists()); assertEquals("test content", Files.readString(downloadFile.toPath()));
内容的提问来源于stack exchange,提问作者Kunal Varpe
相关产品推荐
相关产品推荐

