From ce55fceb16b563154828baef16484768b8c36426 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 23 May 2025 05:36:04 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=9C=E7=A6=BBawssdk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/build.gradle.kts | 5 +- .../core/pack/host/impl/S3Host.java | 235 ------------------ 2 files changed, 2 insertions(+), 238 deletions(-) delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 2ddde0188..b4b762304 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -52,13 +52,12 @@ dependencies { compileOnly("com.mojang:datafixerupper:${rootProject.properties["datafixerupper_version"]}") // Aho-Corasick java implementation compileOnly("org.ahocorasick:ahocorasick:${rootProject.properties["ahocorasick_version"]}") - // Amazon S3 - compileOnly("software.amazon.awssdk:s3:${rootProject.properties["amazon_awssdk_version"]}") - compileOnly("software.amazon.awssdk:netty-nio-client:${rootProject.properties["amazon_awssdk_version"]}") // EvalEx compileOnly("com.ezylang:EvalEx:${rootProject.properties["evalex_version"]}") // Jimfs compileOnly("com.google.jimfs:jimfs:${rootProject.properties["jimfs_version"]}") + // S3 + implementation("net.momirealms:craft-engine-s3:0.1") } java { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java deleted file mode 100644 index 21f20a97d..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ /dev/null @@ -1,235 +0,0 @@ -package net.momirealms.craftengine.core.pack.host.impl; - -import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; -import net.momirealms.craftengine.core.pack.host.ResourcePackHost; -import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; -import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; -import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.plugin.locale.LocalizedException; -import net.momirealms.craftengine.core.util.HashUtils; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; -import net.momirealms.craftengine.core.util.ResourceConfigUtils; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.auth.signer.AwsS3V4Signer; -import software.amazon.awssdk.core.async.AsyncRequestBody; -import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; -import software.amazon.awssdk.http.async.SdkAsyncHttpClient; -import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; -import software.amazon.awssdk.http.nio.netty.ProxyConfiguration; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.s3.S3AsyncClient; -import software.amazon.awssdk.services.s3.S3AsyncClientBuilder; -import software.amazon.awssdk.services.s3.model.HeadObjectRequest; -import software.amazon.awssdk.services.s3.model.NoSuchKeyException; -import software.amazon.awssdk.services.s3.model.PutObjectRequest; -import software.amazon.awssdk.services.s3.presigner.S3Presigner; -import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; - -import java.net.URI; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.time.Duration; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; - -public class S3Host implements ResourcePackHost { - public static final Factory FACTORY = new Factory(); - private final S3AsyncClient s3AsyncClient; - private final S3Presigner preSigner; - private final String bucket; - private final String uploadPath; - private final String cdnDomain; - private final String cdnProtocol; - private final Duration validity; - - public S3Host( - S3AsyncClient s3AsyncClient, - S3Presigner preSigner, - String bucket, - String uploadPath, - String cdnDomain, - String cdnProtocol, - Duration validity - ) { - this.s3AsyncClient = s3AsyncClient; - this.preSigner = preSigner; - this.bucket = bucket; - this.uploadPath = uploadPath; - this.cdnDomain = cdnDomain; - this.cdnProtocol = cdnProtocol; - this.validity = validity; - } - - @Override - public boolean canUpload() { - return true; - } - - @Override - public Key type() { - return ResourcePackHosts.S3; - } - - @Override - public CompletableFuture> requestResourcePackDownloadLink(UUID player) { - return this.s3AsyncClient.headObject(HeadObjectRequest.builder() - .bucket(this.bucket) - .key(this.uploadPath) - .build()) - .handle((headResponse, exception) -> { - if (exception != null) { - Throwable cause = exception.getCause(); - if (cause instanceof NoSuchKeyException) { - CraftEngine.instance().logger().warn("[S3] Resource pack not found in bucket '" + this.bucket + "'. Path: " + this.uploadPath); - return Collections.emptyList(); - } else { - CraftEngine.instance().logger().warn( - "[S3] Failed to retrieve resource pack metadata. Reason: " + - cause.getClass().getSimpleName() + " - " + cause.getMessage() - ); - throw new CompletionException("Metadata request failed for path: " + this.uploadPath, cause); - } - } - String sha1 = headResponse.metadata().get("sha1"); - if (sha1 == null) { - CraftEngine.instance().logger().warn("[S3] Missing SHA-1 checksum in object metadata. Path: " + this.uploadPath); - throw new CompletionException(new IllegalStateException("Missing SHA-1 metadata for S3 object: " + this.uploadPath)); - } - GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() - .signatureDuration(this.validity) - .getObjectRequest(b -> b.bucket(this.bucket).key(this.uploadPath)) - .build(); - return Collections.singletonList( - ResourcePackDownloadData.of( - replaceWithCdnUrl(this.preSigner.presignGetObject(presignRequest).url()), - UUID.nameUUIDFromBytes(sha1.getBytes(StandardCharsets.UTF_8)), - sha1 - ) - ); - }); - } - - @Override - public CompletableFuture upload(Path resourcePackPath) { - String sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); - PutObjectRequest putObjectRequest = PutObjectRequest.builder() - .bucket(this.bucket) - .key(this.uploadPath) - .metadata(Map.of("sha1", sha1)) - .build(); - long uploadStart = System.currentTimeMillis(); - CraftEngine.instance().logger().info("[S3] Initiating resource pack upload to '" + this.uploadPath + "'"); - return this.s3AsyncClient.putObject(putObjectRequest, AsyncRequestBody.fromFile(resourcePackPath)) - .handle((response, exception) -> { - if (exception != null) { - Throwable cause = exception instanceof CompletionException ? - exception.getCause() : - exception; - CraftEngine.instance().logger().warn( - "[S3] Upload failed for path '" + this.uploadPath + "'. Error: " + - cause.getClass().getSimpleName() + " - " + cause.getMessage() - ); - throw new CompletionException("Failed to upload to S3 path: " + this.uploadPath, cause); - } - CraftEngine.instance().logger().info( - "[S3] Successfully uploaded resource pack to '" + this.uploadPath + "' in " + - (System.currentTimeMillis() - uploadStart) + " ms" - ); - return null; - }); - } - - private String replaceWithCdnUrl(URL originalUrl) { - if (this.cdnDomain == null) return originalUrl.toString(); - return this.cdnProtocol + "://" + this.cdnDomain - + originalUrl.getPath() - + (originalUrl.getQuery() != null ? "?" + originalUrl.getQuery() : ""); - } - - public static class Factory implements ResourcePackHostFactory { - - @Override - @SuppressWarnings("deprecation") - public ResourcePackHost create(Map arguments) { - boolean useEnv = (boolean) arguments.getOrDefault("use-environment-variables", false); - String endpoint = Optional.ofNullable(arguments.get("endpoint")).map(String::valueOf).orElse(null); - if (endpoint == null || endpoint.isEmpty()) { - throw new LocalizedException("warning.config.host.s3.missing_endpoint"); - } - String protocol = arguments.getOrDefault("protocol", "https").toString(); - boolean usePathStyle = (boolean) arguments.getOrDefault("path-style", false); - String bucket = Optional.ofNullable(arguments.get("bucket")).map(String::valueOf).orElse(null); - if (bucket == null || bucket.isEmpty()) { - throw new LocalizedException("warning.config.host.s3.missing_bucket"); - } - String region = arguments.getOrDefault("region", "auto").toString(); - String accessKeyId = useEnv ? System.getenv("CE_S3_ACCESS_KEY_ID") : Optional.ofNullable(arguments.get("access-key-id")).map(String::valueOf).orElse(null); - if (accessKeyId == null || accessKeyId.isEmpty()) { - throw new LocalizedException("warning.config.host.s3.missing_access_key"); - } - String accessKeySecret = useEnv ? System.getenv("CE_S3_ACCESS_KEY_SECRET") : Optional.ofNullable(arguments.get("access-key-secret")).map(String::valueOf).orElse(null); - if (accessKeySecret == null || accessKeySecret.isEmpty()) { - throw new LocalizedException("warning.config.host.s3.missing_secret"); - } - String uploadPath = arguments.getOrDefault("upload-path", "craftengine/resource_pack.zip").toString(); - if (uploadPath == null || uploadPath.isEmpty()) { - throw new LocalizedException("warning.config.host.s3.missing_upload_path"); - } - boolean useLegacySignature = (boolean) arguments.getOrDefault("use-legacy-signature", true); - Duration validity = Duration.ofSeconds((int) arguments.getOrDefault("validity", 10)); - - Map cdn = MiscUtils.castToMap(arguments.get("cdn"), true); - String cdnDomain = null; - String cdnProtocol = "https"; - if (cdn != null) { - cdnDomain = Optional.ofNullable(arguments.get("domain")).map(String::valueOf).orElse(null); - cdnProtocol = arguments.getOrDefault("protocol", "https").toString(); - } - - AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKeyId, accessKeySecret); - - S3AsyncClientBuilder s3AsyncClientBuilder = S3AsyncClient.builder() - .endpointOverride(URI.create(protocol + "://" + endpoint)) - .region(Region.of(region)) - .credentialsProvider(StaticCredentialsProvider.create(credentials)) - .serviceConfiguration(b -> b.pathStyleAccessEnabled(usePathStyle)); - - if (useLegacySignature) { - s3AsyncClientBuilder.overrideConfiguration(b -> b - .putAdvancedOption(SdkAdvancedClientOption.SIGNER, AwsS3V4Signer.create()) - ); - } - - Map proxySetting = MiscUtils.castToMap(arguments.get("proxy"), true); - if (proxySetting != null) { - String host = ResourceConfigUtils.requireNonEmptyStringOrThrow(proxySetting.get("host"), () -> new LocalizedException("warning.config.host.proxy.missing_host")); - int port = ResourceConfigUtils.getAsInt(proxySetting.get("port"), "port"); - if (port <= 0 || port > 65535) { - throw new LocalizedException("warning.config.host.proxy.missing_port"); - } - String scheme = ResourceConfigUtils.requireNonEmptyStringOrThrow(proxySetting.get("scheme"), () -> new LocalizedException("warning.config.host.proxy.missing_scheme")); - String username = Optional.ofNullable(proxySetting.get("username")).map(String::valueOf).orElse(null); - String password = Optional.ofNullable(proxySetting.get("password")).map(String::valueOf).orElse(null); - ProxyConfiguration.Builder builder = ProxyConfiguration.builder().host(host).port(port).scheme(scheme); - if (username != null) builder.username(username); - if (password != null) builder.password(password); - SdkAsyncHttpClient httpClient = NettyNioAsyncHttpClient.builder().proxyConfiguration(builder.build()).build(); - s3AsyncClientBuilder.httpClient(httpClient); - } - - S3AsyncClient s3AsyncClient = s3AsyncClientBuilder.build(); - - S3Presigner preSigner = S3Presigner.builder() - .endpointOverride(URI.create(protocol + "://" + endpoint)) - .region(Region.of(region)) - .credentialsProvider(StaticCredentialsProvider.create(credentials)) - .build(); - - return new S3Host(s3AsyncClient, preSigner, bucket, uploadPath, cdnDomain, cdnProtocol, validity); - } - } -} \ No newline at end of file