TestContainers与LocalStack S3:S3 URI解析失败,端点IP不被认可
问题描述
我通过以下代码获取AWS S3对象URL:
s3AsyncClient.utilities() .getUrl(r -> r.region(awsRegion).bucket(bucketName).key(key));
在使用TestContainers + LocalStack S3做集成测试时,返回的URL为:
http://127.0.0.1:51435/my-bucket/games/123.json
但尝试将其转换为S3Uri时:
S3Uri s3Uri = s3AsyncClient.utilities().parseUri(url.toURI());
抛出错误:
java.lang.IllegalArgumentException: Invalid S3 URI: hostname does not appear to be a valid S3 endpoint: http://127.0.0.1:51435/my-bucket/games/123.json
AWS解析器不认可127.0.0.1为有效S3端点,当前LocalStack配置如下:
private static final LocalStackContainer LOCAL_STACK_CONTAINER = new LocalStackContainer(LOCAL_STACK_IMAGE) .withServices(LocalStackContainer.Service.S3); static { LOCAL_STACK_CONTAINER.start(); } @DynamicPropertySource static void s3Properties(final DynamicPropertyRegistry registry) { registry.add("ssp.s3.region", LOCAL_STACK_CONTAINER::getRegion); registry.add("ssp.s3.endpoint", () -> LOCAL_STACK_CONTAINER.getEndpointOverride(LocalStackContainer.Service.S3)); }
解决方案
方案一:用LocalStack测试域名生成虚拟主机格式URL
LocalStack提供官方测试域名localhost.localstack.cloud,配置容器使用该域名,并让AWS SDK生成虚拟主机样式URL,AWS解析器会自动识别为合法端点。
修改LocalStack配置
private static final LocalStackContainer LOCAL_STACK_CONTAINER = new LocalStackContainer(LOCAL_STACK_IMAGE) .withServices(LocalStackContainer.Service.S3) .withEnv("LOCALSTACK_HOSTNAME", "localhost.localstack.cloud");
配置S3AsyncClient启用虚拟主机样式访问
创建客户端时开启虚拟主机模式:
S3AsyncClient s3AsyncClient = S3AsyncClient.builder() .region(Region.of(awsRegion)) .endpointOverride(URI.create(localStackEndpoint)) .serviceConfiguration(config -> config.virtualHostStyleEnabled(true)) .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("test", "test"))) .build();
此时生成的URL会是http://my-bucket.localhost.localstack.cloud:51435/games/123.json,调用parseUri即可正常解析。
方案二:手动解析URL构造S3Uri(测试场景快速解决)
如果不想修改容器和客户端配置,可直接绕过AWS SDK的parseUri方法,手动解析URL路径构造S3Uri:
URI uri = url.toURI(); String path = uri.getPath().substring(1); // 移除路径开头的/ int bucketSeparatorIndex = path.indexOf('/'); String bucketName; String key; if (bucketSeparatorIndex == -1) { bucketName = path; key = ""; } else { bucketName = path.substring(0, bucketSeparatorIndex); key = path.substring(bucketSeparatorIndex + 1); } S3Uri s3Uri = S3Uri.builder().bucket(bucketName).key(key).build();
这种方式适合测试场景,无需调整现有容器或客户端配置。
方案三:配置SDK允许自定义端点解析
通过自定义EndpointProvider让AWS SDK认可127.0.0.1为合法S3端点,适合需要严格模拟生产解析逻辑的场景:
S3AsyncClient s3AsyncClient = S3AsyncClient.builder() .region(Region.of(awsRegion)) .endpointOverride(URI.create(localStackEndpoint)) .endpointProvider((endpointParams, context) -> CompletableFuture.completedFuture( Endpoint.builder() .uri(URI.create(localStackEndpoint)) .build() )) .serviceConfiguration(config -> config.pathStyleAccessEnabled(true)) .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("test", "test"))) .build();
同时,解析时使用S3UriParser的重载方法指定允许的端点:
S3UriParser parser = S3UriParser.create(); S3Uri s3Uri = parser.parseUri(url.toURI(), ParseUriRequest.builder().allowedEndpoints(Set.of(localStackEndpoint)).build());
内容的提问来源于stack exchange,提问作者user471011
相关产品推荐
相关产品推荐

