This is an automated email from the ASF dual-hosted git repository.

liaoxin01 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 07ba0d1e448 [improvement](s3) Default scheme-less S3 endpoints to 
HTTPS (#65379)
07ba0d1e448 is described below

commit 07ba0d1e448be58bef46e17c01498cb3d9864891
Author: Refrain <[email protected]>
AuthorDate: Tue Aug 4 23:26:01 2026 +0800

    [improvement](s3) Default scheme-less S3 endpoints to HTTPS (#65379)
    
    Problem Summary:
    
    S3 endpoint scheme handling was inconsistent across FE, BE, and Cloud,
    and the previous implementation distinguished Doris internal buckets
    from user-configured external buckets. That classification leaked into
    S3Conf construction and client-cache identity even though the desired
    transport rule is the same for every AWS/S3-compatible endpoint.
    
    This PR applies one rule across S3 client construction and
    Resource/Storage Vault persistence boundaries:
    
    - Keep the existing FE, BE, and Cloud s3_client_http_scheme
    compatibility setting.
    - Default every endpoint without an explicit scheme to HTTPS.
    - If s3_client_http_scheme is explicitly set to http, use HTTP for
    endpoints without a scheme.
    - Preserve endpoints that already contain an explicit http:// or
    https:// scheme.
    - Remove the internal/external bucket classification from BE and Cloud
    S3 configuration.
    - Apply the same behavior to FE native S3 clients, the S3 filesystem
    plugin, connectivity checks, stages, resources, and Iceberg S3 FileIO
    handoffs.
    - Normalize scheme-less S3 Resource and Storage Vault endpoint metadata
    before persistence using the same configured default.
    
    Azure is intentionally unchanged in this PR. Its existing
    provider-specific behavior already defaults a bare endpoint to HTTPS and
    preserves an explicit scheme; whether Azure should share the S3
    compatibility setting is a separate policy decision.
    
    ### Release note
    
    S3 endpoints without an explicit scheme now use HTTPS by default across
    FE, BE, and Cloud. Set s3_client_http_scheme = http for compatibility
    with scheme-less HTTP endpoints. Explicit HTTP and HTTPS endpoint
    schemes are unchanged.
---
 be/src/common/config.cpp                           |  4 +-
 be/src/common/config.h                             |  2 +-
 be/src/util/s3_util.cpp                            |  4 +-
 be/test/io/s3_client_factory_test.cpp              | 29 +++++++++++
 cloud/src/common/config.h                          |  4 +-
 cloud/src/recycler/s3_accessor.cpp                 |  4 +-
 cloud/test/util_test.cpp                           | 30 +++++++++++
 common/cpp/aws_common.cpp                          | 10 ++++
 common/cpp/aws_common.h                            | 38 ++++++++------
 .../main/java/org/apache/doris/common/Config.java  |  5 ++
 .../java/org/apache/doris/catalog/S3Resource.java  | 29 ++++++-----
 .../java/org/apache/doris/common/util/S3Util.java  | 22 +++++---
 .../doris/catalog/S3ResourcePersistParityTest.java | 13 +++--
 .../org/apache/doris/catalog/S3ResourceTest.java   | 22 ++++++--
 .../org/apache/doris/common/util/S3UtilTest.java   | 58 +++++++++++++++++++++-
 .../pipeline/vault_p0/conf/be_custom.conf          |  1 +
 .../pipeline/vault_p0/conf/fe_custom.conf          |  1 +
 .../pipeline/vault_p0/conf/recycler_custom.conf    |  1 +
 .../cold_heat_separation/policy/alter.groovy       |  2 +-
 19 files changed, 222 insertions(+), 57 deletions(-)

diff --git a/be/src/common/config.cpp b/be/src/common/config.cpp
index dede5133117..f365dab488a 100644
--- a/be/src/common/config.cpp
+++ b/be/src/common/config.cpp
@@ -1653,8 +1653,8 @@ DEFINE_Validator(paimon_file_system_scheme_mappings,
 
 DEFINE_mInt32(thrift_client_open_num_tries, "1");
 
-// http scheme in S3Client to use. E.g. http or https
-DEFINE_String(s3_client_http_scheme, "http");
+// Default HTTP scheme used by S3Client when the endpoint has no scheme.
+DEFINE_String(s3_client_http_scheme, "https");
 DEFINE_Validator(s3_client_http_scheme, [](const std::string& config) -> bool {
     return config == "http" || config == "https";
 });
diff --git a/be/src/common/config.h b/be/src/common/config.h
index 9954f3b3e41..699bbdbee47 100644
--- a/be/src/common/config.h
+++ b/be/src/common/config.h
@@ -1714,7 +1714,7 @@ DECLARE_Strings(paimon_file_system_scheme_mappings);
 // Retry the Open num_retries time waiting 100 milliseconds between retries.
 DECLARE_mInt32(thrift_client_open_num_tries);
 
-// http scheme in S3Client to use. E.g. http or https
+// Default HTTP scheme used by S3Client when the endpoint has no scheme.
 DECLARE_String(s3_client_http_scheme);
 
 DECLARE_mBool(ignore_schema_change_check);
diff --git a/be/src/util/s3_util.cpp b/be/src/util/s3_util.cpp
index 1e03308fae4..fe9af8f582e 100644
--- a/be/src/util/s3_util.cpp
+++ b/be/src/util/s3_util.cpp
@@ -446,9 +446,7 @@ std::shared_ptr<io::ObjStorageClient> 
S3ClientFactory::_create_s3_client(
         aws_config.connectTimeoutMs = s3_conf.connect_timeout_ms;
     }
 
-    if (config::s3_client_http_scheme == "http") {
-        aws_config.scheme = Aws::Http::Scheme::HTTP;
-    }
+    set_s3_client_default_http_scheme(aws_config, 
config::s3_client_http_scheme);
 
     aws_config.retryStrategy = std::make_shared<S3CustomRetryStrategy>(
             config::max_s3_client_retry /*scaleFactor = 25*/, 
/*retry_slow_down=*/true);
diff --git a/be/test/io/s3_client_factory_test.cpp 
b/be/test/io/s3_client_factory_test.cpp
index de8149380cb..53787e7150e 100644
--- a/be/test/io/s3_client_factory_test.cpp
+++ b/be/test/io/s3_client_factory_test.cpp
@@ -17,6 +17,7 @@
 
 #include <aws/core/auth/AWSCredentialsProviderChain.h>
 #include <aws/core/auth/STSCredentialsProvider.h>
+#include <aws/core/client/ClientConfiguration.h>
 #include <aws/identity-management/auth/STSAssumeRoleCredentialsProvider.h>
 #include <gtest/gtest.h>
 
@@ -26,6 +27,7 @@
 #include <vector>
 
 #include "cloud/config.h"
+#include "cpp/aws_common.h"
 #include "cpp/custom_aws_credentials_provider_chain.h"
 #include "io/fs/rate_limited_obj_storage_client.h"
 #include "io/fs/s3_obj_storage_client.h"
@@ -232,6 +234,32 @@ TEST_F(S3ClientFactoryTest, AwsCredentialsProvider) {
     config::aws_credentials_provider_version = "v2";
 }
 
+TEST_F(S3ClientFactoryTest, SetS3ClientDefaultHttpScheme) {
+    S3ClientFactory::instance();
+    Aws::Client::ClientConfiguration client_config;
+    client_config.endpointOverride = "example.com:9000";
+
+    set_s3_client_default_http_scheme(client_config, "http");
+    EXPECT_EQ(client_config.endpointOverride, "example.com:9000");
+    EXPECT_EQ(client_config.scheme, Aws::Http::Scheme::HTTP);
+
+    set_s3_client_default_http_scheme(client_config, "https");
+    EXPECT_EQ(client_config.endpointOverride, "example.com:9000");
+    EXPECT_EQ(client_config.scheme, Aws::Http::Scheme::HTTPS);
+
+    client_config.endpointOverride = "http://example.com:9000";;
+    client_config.scheme = Aws::Http::Scheme::HTTP;
+    set_s3_client_default_http_scheme(client_config, "https");
+    EXPECT_EQ(client_config.endpointOverride, "http://example.com:9000";);
+    EXPECT_EQ(client_config.scheme, Aws::Http::Scheme::HTTP);
+
+    client_config.endpointOverride = "https://example.com:9000";;
+    client_config.scheme = Aws::Http::Scheme::HTTPS;
+    set_s3_client_default_http_scheme(client_config, "http");
+    EXPECT_EQ(client_config.endpointOverride, "https://example.com:9000";);
+    EXPECT_EQ(client_config.scheme, Aws::Http::Scheme::HTTPS);
+}
+
 TEST_F(S3ClientFactoryTest, ConvertPropertiesToS3ConfRoleArnProviderType) {
     std::map<std::string, std::string> properties {
             {"AWS_ENDPOINT", "s3.us-west-2.amazonaws.com"},
@@ -244,6 +272,7 @@ TEST_F(S3ClientFactoryTest, 
ConvertPropertiesToS3ConfRoleArnProviderType) {
 
     S3Conf s3_conf;
     ASSERT_TRUE(S3ClientFactory::convert_properties_to_s3_conf(properties, 
s3_uri, &s3_conf).ok());
+    ASSERT_EQ(s3_conf.client_conf.endpoint, properties.at("AWS_ENDPOINT"));
     ASSERT_EQ(s3_conf.client_conf.cred_provider_type, 
CredProviderType::Default);
 
     properties["AWS_CREDENTIALS_PROVIDER_TYPE"] = "WEB_IDENTITY";
diff --git a/cloud/src/common/config.h b/cloud/src/common/config.h
index 37069faa706..070cdd4e44e 100644
--- a/cloud/src/common/config.h
+++ b/cloud/src/common/config.h
@@ -327,8 +327,8 @@ CONF_String(priority_networks, "");
 
 CONF_Bool(enable_cluster_name_check, "false");
 
-// http scheme in S3Client to use. E.g. http or https
-CONF_String(s3_client_http_scheme, "http");
+// Default HTTP scheme used by S3Client when the endpoint has no scheme.
+CONF_String(s3_client_http_scheme, "https");
 CONF_Validator(s3_client_http_scheme, [](const std::string& config) -> bool {
     return config == "http" || config == "https";
 });
diff --git a/cloud/src/recycler/s3_accessor.cpp 
b/cloud/src/recycler/s3_accessor.cpp
index 7c4fde97041..0c3713d92d1 100644
--- a/cloud/src/recycler/s3_accessor.cpp
+++ b/cloud/src/recycler/s3_accessor.cpp
@@ -428,9 +428,7 @@ int S3Accessor::init() {
                                                     
config::instance_recycler_worker_pool_size),
                                              (long)aws_config.maxConnections);
 
-        if (config::s3_client_http_scheme == "http") {
-            aws_config.scheme = Aws::Http::Scheme::HTTP;
-        }
+        set_s3_client_default_http_scheme(aws_config, 
config::s3_client_http_scheme);
         // Recycler should fail fast on S3 SlowDown instead of retrying and 
blocking worker threads.
         aws_config.retryStrategy = std::make_shared<S3CustomRetryStrategy>(
                 config::max_s3_client_retry, /*retry_slow_down=*/false);
diff --git a/cloud/test/util_test.cpp b/cloud/test/util_test.cpp
index 435feb6e0c3..3ef53ad358f 100644
--- a/cloud/test/util_test.cpp
+++ b/cloud/test/util_test.cpp
@@ -17,6 +17,8 @@
 
 #include "cpp/util.h"
 
+#include <aws/core/client/ClientConfiguration.h>
+
 #include <chrono>
 #include <stdexcept>
 #include <string>
@@ -33,6 +35,7 @@
 #include "cpp/sync_point.h"
 #include "gtest/gtest.h"
 #include "recycler/recycler.h"
+#include "recycler/s3_accessor.h"
 #include "recycler/sync_executor.h"
 
 using namespace doris::cloud;
@@ -438,6 +441,33 @@ TEST(UtilTest, test_normalize_http_uri) {
               "https://example.com/path?query=value#fragment";);
 }
 
+TEST(UtilTest, test_set_s3_client_default_http_scheme) {
+    doris::cloud::S3Environment::getInstance();
+    Aws::Client::ClientConfiguration client_config =
+            doris::cloud::S3Environment::getClientConfiguration();
+
+    client_config.endpointOverride = "example.com:9000";
+    doris::set_s3_client_default_http_scheme(client_config, "http");
+    EXPECT_EQ(client_config.endpointOverride, "example.com:9000");
+    EXPECT_EQ(client_config.scheme, Aws::Http::Scheme::HTTP);
+
+    doris::set_s3_client_default_http_scheme(client_config, "https");
+    EXPECT_EQ(client_config.endpointOverride, "example.com:9000");
+    EXPECT_EQ(client_config.scheme, Aws::Http::Scheme::HTTPS);
+
+    client_config.endpointOverride = "http://example.com:9000";;
+    client_config.scheme = Aws::Http::Scheme::HTTP;
+    doris::set_s3_client_default_http_scheme(client_config, "https");
+    EXPECT_EQ(client_config.endpointOverride, "http://example.com:9000";);
+    EXPECT_EQ(client_config.scheme, Aws::Http::Scheme::HTTP);
+
+    client_config.endpointOverride = "https://example.com:9000";;
+    client_config.scheme = Aws::Http::Scheme::HTTPS;
+    doris::set_s3_client_default_http_scheme(client_config, "http");
+    EXPECT_EQ(client_config.endpointOverride, "https://example.com:9000";);
+    EXPECT_EQ(client_config.scheme, Aws::Http::Scheme::HTTPS);
+}
+
 TEST(UtilTest, test_long_normalize_http_uri) {
     std::string longPath = "https://example.com";;
     for (int i = 0; i < 100; i++) {
diff --git a/common/cpp/aws_common.cpp b/common/cpp/aws_common.cpp
index 3c7f5a0eda5..7f11d8cb95e 100644
--- a/common/cpp/aws_common.cpp
+++ b/common/cpp/aws_common.cpp
@@ -17,6 +17,7 @@
 
 #include "aws_common.h"
 
+#include <aws/core/client/ClientConfiguration.h>
 #include <glog/logging.h>
 
 namespace doris {
@@ -84,4 +85,13 @@ std::string get_valid_ca_cert_path(const 
std::vector<std::string>& ca_cert_file_
     }
     return "";
 }
+
+void set_s3_client_default_http_scheme(Aws::Client::ClientConfiguration& 
client_config,
+                                       const std::string& scheme) {
+    if (client_config.endpointOverride.starts_with("http://";) ||
+        client_config.endpointOverride.starts_with("https://";)) {
+        return;
+    }
+    client_config.scheme = scheme == "http" ? Aws::Http::Scheme::HTTP : 
Aws::Http::Scheme::HTTPS;
 }
+} // namespace doris
diff --git a/common/cpp/aws_common.h b/common/cpp/aws_common.h
index cc48e23c2f9..d977dd245b0 100644
--- a/common/cpp/aws_common.h
+++ b/common/cpp/aws_common.h
@@ -21,23 +21,31 @@
 
 #include <filesystem>
 
+namespace Aws::Client {
+struct ClientConfiguration;
+}
+
 namespace doris {
-    //AWS Credentials Provider Type
-    enum class CredProviderType {
-        Default = 0,
-        Simple = 1,
-        InstanceProfile = 2,
-        Env = 3,
-        SystemProperties = 4,
-        WebIdentity = 5,
-        Container = 6,
-        Anonymous = 7
-    };
+//AWS Credentials Provider Type
+enum class CredProviderType {
+    Default = 0,
+    Simple = 1,
+    InstanceProfile = 2,
+    Env = 3,
+    SystemProperties = 4,
+    WebIdentity = 5,
+    Container = 6,
+    Anonymous = 7
+};
+
+CredProviderType cred_provider_type_from_pb(cloud::CredProviderTypePB 
cred_provider_type);
 
-    CredProviderType cred_provider_type_from_pb(cloud::CredProviderTypePB 
cred_provider_type);
+CredProviderType cred_provider_type_from_string(const std::string& type);
 
-    CredProviderType cred_provider_type_from_string(const std::string& type);
+std::string get_valid_ca_cert_path(const std::vector<std::string>& 
ca_cert_file_paths);
 
-    std::string get_valid_ca_cert_path(const std::vector<std::string>& 
ca_cert_file_paths);
+// Configures the default S3 client transport scheme for endpoints without an 
explicit scheme.
+void set_s3_client_default_http_scheme(Aws::Client::ClientConfiguration& 
client_config,
+                                       const std::string& scheme);
 
-    } // namespace doris
\ No newline at end of file
+} // namespace doris
\ No newline at end of file
diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java 
b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
index 133ee2114f7..765baf5393d 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
@@ -3278,6 +3278,11 @@ public class Config extends ConfigBase {
                     + "it is intentionally not modifiable at runtime via ADMIN 
SET FRONTEND CONFIG."})
     public static String[] s3_load_endpoint_white_list = {};
 
+    @ConfField(description = {
+            "The default scheme for S3 endpoints without an explicit scheme. 
Valid values are http and https."},
+            options = {"http", "https"})
+    public static String s3_client_http_scheme = "https";
+
     @ConfField(mutable = true, description = {
             "For deterministic S3 paths (without wildcards like *, ?), use 
HEAD requests instead of "
                     + "ListObjects to avoid requiring ListBucket permission. 
Brace patterns {1,2,3} and "
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/S3Resource.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/S3Resource.java
index fce8d09a837..0c70cd67ce4 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/S3Resource.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/S3Resource.java
@@ -21,6 +21,7 @@ import org.apache.doris.common.DdlException;
 import org.apache.doris.common.credentials.CloudCredentialWithEndpoint;
 import org.apache.doris.common.proc.BaseProcResult;
 import org.apache.doris.common.util.DatasourcePrintableMap;
+import org.apache.doris.common.util.S3Util;
 import org.apache.doris.datasource.storage.S3ResourceCompat;
 import org.apache.doris.filesystem.UploadPartResult;
 import org.apache.doris.filesystem.spi.ObjFileSystem;
@@ -102,20 +103,19 @@ public class S3Resource extends Resource {
             LOG.debug("s3 info need check validity : {}", needCheck);
         }
 
-        // the endpoint for ping need add uri scheme.
-        String pingEndpoint = properties.get(S3ResourceCompat.ENDPOINT);
-        if (!pingEndpoint.startsWith("http://";) && 
!pingEndpoint.startsWith("https://";)) {
-            pingEndpoint = "http://"; + 
properties.get(S3ResourceCompat.ENDPOINT);
-            properties.put(S3ResourceCompat.ENDPOINT, pingEndpoint);
-            properties.put(S3ResourceCompat.Env.ENDPOINT, pingEndpoint);
-        }
-        String region = S3ResourceCompat.getRegionOfEndpoint(pingEndpoint);
+        String endpoint = properties.get(S3ResourceCompat.ENDPOINT);
+        properties.put(S3ResourceCompat.Env.ENDPOINT, endpoint);
+        String region = S3ResourceCompat.getRegionOfEndpoint(endpoint);
         properties.putIfAbsent(S3ResourceCompat.REGION, region);
 
         if (needCheck) {
+            Map<String, String> pingProperties = new HashMap<>(properties);
+            String pingEndpoint = S3Util.buildEndpointUrl(endpoint);
+            pingProperties.put(S3ResourceCompat.ENDPOINT, pingEndpoint);
+            pingProperties.put(S3ResourceCompat.Env.ENDPOINT, pingEndpoint);
             String bucketName = properties.get(S3ResourceCompat.BUCKET);
             String rootPath = properties.get(S3ResourceCompat.ROOT_PATH);
-            pingS3(bucketName, rootPath, properties);
+            pingS3(bucketName, rootPath, pingProperties);
         }
         // optional
         S3ResourceCompat.optionalS3Property(properties);
@@ -233,6 +233,9 @@ public class S3Resource extends Resource {
         }
         // compatible with old version, Need convert if modified properties 
map uses old properties.
         S3ResourceCompat.convertToStdProperties(properties);
+        if (!Strings.isNullOrEmpty(properties.get(S3ResourceCompat.ENDPOINT))) 
{
+            properties.put(S3ResourceCompat.Env.ENDPOINT, 
properties.get(S3ResourceCompat.ENDPOINT));
+        }
         boolean needCheck = isNeedCheck(properties);
         if (LOG.isDebugEnabled()) {
             LOG.debug("s3 info need check validity : {}", needCheck);
@@ -241,6 +244,9 @@ public class S3Resource extends Resource {
             S3ResourceCompat.requiredS3PingProperties(this.properties);
             Map<String, String> changedProperties = new 
HashMap<>(this.properties);
             changedProperties.putAll(properties);
+            String endpoint = 
S3Util.buildEndpointUrl(changedProperties.get(S3ResourceCompat.ENDPOINT));
+            changedProperties.put(S3ResourceCompat.ENDPOINT, endpoint);
+            changedProperties.put(S3ResourceCompat.Env.ENDPOINT, endpoint);
             String bucketName = 
properties.getOrDefault(S3ResourceCompat.BUCKET,
                     this.properties.get(S3ResourceCompat.BUCKET));
             String rootPath = 
properties.getOrDefault(S3ResourceCompat.ROOT_PATH,
@@ -289,10 +295,9 @@ public class S3Resource extends Resource {
                 this.properties.get(S3ResourceCompat.SESSION_TOKEN));
         String endpoint = properties.getOrDefault(S3ResourceCompat.ENDPOINT,
                 this.properties.get(S3ResourceCompat.ENDPOINT));
-        String pingEndpoint = "http://"; + endpoint;
-        String region = S3ResourceCompat.getRegionOfEndpoint(pingEndpoint);
+        String region = S3ResourceCompat.getRegionOfEndpoint(endpoint);
         properties.putIfAbsent(S3ResourceCompat.REGION, region);
-        return new CloudCredentialWithEndpoint(pingEndpoint, region, ak, sk, 
token);
+        return new CloudCredentialWithEndpoint(endpoint, region, ak, sk, 
token);
     }
 
     private boolean isNeedCheck(Map<String, String> newProperties) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/util/S3Util.java 
b/fe/fe-core/src/main/java/org/apache/doris/common/util/S3Util.java
index 3e4f4e7a62f..f6c2fcf9c7c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/util/S3Util.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/S3Util.java
@@ -89,7 +89,7 @@ public class S3Util {
     }
 
     @Deprecated
-    public static S3Client buildS3Client(URI endpoint, String region, 
CloudCredential credential,
+    public static S3Client buildS3Client(String endpoint, String region, 
CloudCredential credential,
             boolean isUsePathStyle) {
         EqualJitterBackoffStrategy backoffStrategy = EqualJitterBackoffStrategy
                 .builder()
@@ -112,7 +112,7 @@ public class S3Util {
         return S3Client.builder()
                 
.httpClient(UrlConnectionHttpClient.builder().socketTimeout(Duration.ofSeconds(30))
                         .connectionTimeout(Duration.ofSeconds(30)).build())
-                .endpointOverride(endpoint)
+                .endpointOverride(URI.create(buildEndpointUrl(endpoint)))
                 .credentialsProvider(getAwsCredencialsProvider(credential))
                 .region(Region.of(region))
                 .overrideConfiguration(clientConf)
@@ -215,7 +215,7 @@ public class S3Util {
                 sessionToken, roleArn, externalId);
     }
 
-    public static S3Client buildS3Client(URI endpoint, String region, boolean 
isUsePathStyle,
+    public static S3Client buildS3Client(String endpoint, String region, 
boolean isUsePathStyle,
                                          AwsCredentialsProvider credential) {
         EqualJitterBackoffStrategy backoffStrategy = EqualJitterBackoffStrategy
                 .builder()
@@ -238,7 +238,7 @@ public class S3Util {
         return S3Client.builder()
                 
.httpClient(UrlConnectionHttpClient.builder().socketTimeout(Duration.ofSeconds(30))
                         .connectionTimeout(Duration.ofSeconds(30)).build())
-                .endpointOverride(endpoint)
+                .endpointOverride(URI.create(buildEndpointUrl(endpoint)))
                 .credentialsProvider(credential)
                 .region(Region.of(region))
                 .overrideConfiguration(clientConf)
@@ -250,7 +250,7 @@ public class S3Util {
                 .build();
     }
 
-    public static S3Client buildS3Client(URI endpoint, String region, boolean 
isUsePathStyle, String accessKey,
+    public static S3Client buildS3Client(String endpoint, String region, 
boolean isUsePathStyle, String accessKey,
             String secretKey, String sessionToken, String roleArn, String 
externalId) {
         EqualJitterBackoffStrategy backoffStrategy = EqualJitterBackoffStrategy
                 .builder()
@@ -270,11 +270,12 @@ public class S3Util {
                 // using AwsS3V4Signer
                 .putAdvancedOption(SdkAdvancedClientOption.SIGNER, 
AwsS3V4Signer.create())
                 .build();
+        URI endpointUri = URI.create(buildEndpointUrl(endpoint));
         return S3Client.builder()
                 
.httpClient(UrlConnectionHttpClient.builder().socketTimeout(Duration.ofSeconds(30))
                         .connectionTimeout(Duration.ofSeconds(30)).build())
-                .endpointOverride(endpoint)
-                .credentialsProvider(getAwsCredencialsProvider(endpoint, 
region, accessKey, secretKey,
+                .endpointOverride(endpointUri)
+                .credentialsProvider(getAwsCredencialsProvider(endpointUri, 
region, accessKey, secretKey,
                         sessionToken, roleArn, externalId))
                 .region(Region.of(region))
                 .overrideConfiguration(clientConf)
@@ -434,6 +435,13 @@ public class S3Util {
         }
     }
 
+    public static String buildEndpointUrl(String endpoint) {
+        if (endpoint.startsWith("http://";) || endpoint.startsWith("https://";)) 
{
+            return endpoint;
+        }
+        return Config.s3_client_http_scheme + "://" + endpoint;
+    }
+
     /**
      * Check if a path pattern is deterministic, meaning all file paths can be 
determined
      * without listing. A pattern is deterministic if it contains no true 
wildcard characters
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/S3ResourcePersistParityTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/S3ResourcePersistParityTest.java
index a6f62f0a904..68ea4046de4 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/S3ResourcePersistParityTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/S3ResourcePersistParityTest.java
@@ -53,9 +53,9 @@ public class S3ResourcePersistParityTest {
         resource.setProperties(ImmutableMap.copyOf(input));
 
         Map<String, String> golden = new HashMap<>();
-        // Dual-namespace write: the ping endpoint gets an http:// prefix and 
lands under BOTH keys.
-        golden.put("s3.endpoint", "http://s3.us-east-1.amazonaws.com";);
-        golden.put("AWS_ENDPOINT", "http://s3.us-east-1.amazonaws.com";);
+        // Dual-namespace write preserves the user-provided endpoint under 
BOTH keys.
+        golden.put("s3.endpoint", "s3.us-east-1.amazonaws.com");
+        golden.put("AWS_ENDPOINT", "s3.us-east-1.amazonaws.com");
         // Region derived from the endpoint host (second dot-segment).
         golden.put("s3.region", "us-east-1");
         golden.put("s3.access_key", "myAk");
@@ -85,10 +85,9 @@ public class S3ResourcePersistParityTest {
         resource.setProperties(ImmutableMap.copyOf(input));
 
         Map<String, String> golden = new HashMap<>();
-        // convertToStdProperties mirrors legacy AWS_* keys into the s3.* 
namespace first;
-        // the endpoint then gets the http:// prefix under both keys.
-        golden.put("AWS_ENDPOINT", "http://s3.us-east-1.amazonaws.com";);
-        golden.put("s3.endpoint", "http://s3.us-east-1.amazonaws.com";);
+        // convertToStdProperties mirrors the original legacy endpoint into 
the s3.* namespace.
+        golden.put("AWS_ENDPOINT", "s3.us-east-1.amazonaws.com");
+        golden.put("s3.endpoint", "s3.us-east-1.amazonaws.com");
         golden.put("s3.region", "us-east-1");
         golden.put("AWS_ACCESS_KEY", "myAk");
         golden.put("s3.access_key", "myAk");
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/S3ResourceTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/S3ResourceTest.java
index ac2bf9fc1ff..acdd7dedec9 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/catalog/S3ResourceTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/S3ResourceTest.java
@@ -21,6 +21,7 @@ import org.apache.doris.common.DdlException;
 import org.apache.doris.common.FeConstants;
 import org.apache.doris.common.FeMetaVersion;
 import org.apache.doris.common.UserException;
+import org.apache.doris.datasource.storage.CloudObjectStoreAdapter;
 import org.apache.doris.datasource.storage.S3ResourceCompat;
 import org.apache.doris.meta.MetaContext;
 import org.apache.doris.mysql.privilege.AccessControllerManager;
@@ -194,7 +195,9 @@ public class S3ResourceTest {
         Assert.assertEquals("s3_1", rS3Resource1.getName());
         Assert.assertEquals("s3_2", rS3Resource2.getName());
 
-        
Assert.assertEquals(rS3Resource2.getProperty(S3ResourceCompat.ENDPOINT), 
"http://aaa";);
+        Assert.assertEquals("aaa", 
rS3Resource2.getProperty(S3ResourceCompat.ENDPOINT));
+        Assert.assertEquals("aaa",
+                
CloudObjectStoreAdapter.getObjStoreInfoPB(rS3Resource2.getCopiedProperties()).getEndpoint());
         Assert.assertEquals(rS3Resource2.getProperty(S3ResourceCompat.REGION), 
"bbb");
         
Assert.assertEquals(rS3Resource2.getProperty(S3ResourceCompat.ROOT_PATH), 
"/path/to/root");
         
Assert.assertEquals(rS3Resource2.getProperty(S3ResourceCompat.ACCESS_KEY), 
"xxx");
@@ -226,11 +229,24 @@ public class S3ResourceTest {
         Map<String, String> modify = new HashMap<>();
         modify.put("s3.access_key", "aaa");
         s3Resource.modifyProperties(modify);
+
+        modify.clear();
+        modify.put(S3ResourceCompat.ENDPOINT, "new-endpoint");
+        s3Resource.modifyProperties(modify);
+        Assert.assertEquals("new-endpoint", 
s3Resource.getProperty(S3ResourceCompat.ENDPOINT));
+        Assert.assertEquals("new-endpoint", 
s3Resource.getProperty(S3ResourceCompat.Env.ENDPOINT));
+        Assert.assertEquals("new-endpoint",
+                
CloudObjectStoreAdapter.getObjStoreInfoPB(s3Resource.getCopiedProperties()).getEndpoint());
+
+        modify.clear();
+        modify.put(S3ResourceCompat.Env.ENDPOINT, "http://other-endpoint";);
+        s3Resource.modifyProperties(modify);
+        Assert.assertEquals("http://other-endpoint";, 
s3Resource.getProperty(S3ResourceCompat.ENDPOINT));
+        Assert.assertEquals("http://other-endpoint";, 
s3Resource.getProperty(S3ResourceCompat.Env.ENDPOINT));
     }
 
     @Test
-    public void testHttpScheme() throws DdlException {
-        // if https:// is set, it should be replaced with http://
+    public void testExplicitSchemeIsPreserved() throws DdlException {
         ImmutableMap<String, String> properties = ImmutableMap.of(
                 "AWS_ENDPOINT", "https://aaa";,
                 "AWS_REGION", "bbb",
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/common/util/S3UtilTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/common/util/S3UtilTest.java
index 4b976ed86cd..ff155a0b1f8 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/common/util/S3UtilTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/common/util/S3UtilTest.java
@@ -17,13 +17,70 @@
 
 package org.apache.doris.common.util;
 
+import org.apache.doris.common.Config;
+
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.services.s3.S3Client;
 
+import java.net.URI;
 import java.util.Arrays;
 import java.util.List;
 
 public class S3UtilTest {
+    private String originalS3ClientHttpScheme;
+
+    @Before
+    public void setUp() {
+        originalS3ClientHttpScheme = Config.s3_client_http_scheme;
+    }
+
+    @After
+    public void tearDown() {
+        Config.s3_client_http_scheme = originalS3ClientHttpScheme;
+    }
+
+    @Test
+    public void testBuildEndpointUrlDefaultsToHttps() {
+        Config.s3_client_http_scheme = "https";
+        Assert.assertEquals("https://s3.us-east-1.amazonaws.com";,
+                S3Util.buildEndpointUrl("s3.us-east-1.amazonaws.com"));
+    }
+
+    @Test
+    public void testBuildEndpointUrlUsesConfiguredHttpScheme() {
+        Config.s3_client_http_scheme = "http";
+        Assert.assertEquals("http://127.0.0.1:9000";,
+                S3Util.buildEndpointUrl("127.0.0.1:9000"));
+    }
+
+    @Test
+    public void testBuildEndpointUrlPreservesExplicitSchemes() {
+        Config.s3_client_http_scheme = "https";
+        Assert.assertEquals("http://127.0.0.1:9000";,
+                S3Util.buildEndpointUrl("http://127.0.0.1:9000";));
+
+        Config.s3_client_http_scheme = "http";
+        Assert.assertEquals("https://s3.us-east-1.amazonaws.com";,
+                S3Util.buildEndpointUrl("https://s3.us-east-1.amazonaws.com";));
+    }
+
+    @Test
+    public void testBuildS3ClientAppliesDefaultSchemeAtClientCreation() {
+        Config.s3_client_http_scheme = "https";
+        try (S3Client client = S3Util.buildS3Client(
+                "127.0.0.1:9000",
+                "us-east-1",
+                true,
+                
StaticCredentialsProvider.create(AwsBasicCredentials.create("ak", "sk")))) {
+            Assert.assertEquals(URI.create("https://127.0.0.1:9000";),
+                    
client.serviceClientConfiguration().endpointOverride().orElseThrow());
+        }
+    }
 
     @Test
     public void testExtendGlobNumberRange_simpleRange() {
@@ -457,4 +514,3 @@ public class S3UtilTest {
         Assert.assertEquals("file[abc.csv", 
S3Util.expandBracketPatterns("file[abc.csv"));
     }
 }
-
diff --git a/regression-test/pipeline/vault_p0/conf/be_custom.conf 
b/regression-test/pipeline/vault_p0/conf/be_custom.conf
index 779eb5a77a5..3a2a0e16213 100644
--- a/regression-test/pipeline/vault_p0/conf/be_custom.conf
+++ b/regression-test/pipeline/vault_p0/conf/be_custom.conf
@@ -11,6 +11,7 @@ enable_file_cache=true
 enable_file_cache_query_limit=true
 file_cache_max_file_segment_size=1048576
 s3_write_buffer_whole_size=52428800
+s3_client_http_scheme = http
 enable_vertical_compaction=true
 fuzzy_vertical_compaction=true
 vacuum_stale_rowsets_interval_seconds=60
diff --git a/regression-test/pipeline/vault_p0/conf/fe_custom.conf 
b/regression-test/pipeline/vault_p0/conf/fe_custom.conf
index f62ffa19fc7..9b622795168 100644
--- a/regression-test/pipeline/vault_p0/conf/fe_custom.conf
+++ b/regression-test/pipeline/vault_p0/conf/fe_custom.conf
@@ -12,6 +12,7 @@ enable_mtmv = true
 remote_fragment_exec_timeout_ms=60000
 dynamic_partition_check_interval_seconds=10
 use_fuzzy_session_variable=true
+s3_client_http_scheme = http
 
 enable_cloud_snapshot_version = true
 enable_auto_collect_statistics = false
diff --git a/regression-test/pipeline/vault_p0/conf/recycler_custom.conf 
b/regression-test/pipeline/vault_p0/conf/recycler_custom.conf
index 62deff2b870..3c3dc627122 100644
--- a/regression-test/pipeline/vault_p0/conf/recycler_custom.conf
+++ b/regression-test/pipeline/vault_p0/conf/recycler_custom.conf
@@ -1,2 +1,3 @@
 # below lines will be appended to the default doris_cloud.conf when deploying 
recycler
 brpc_listen_port = 6000
+s3_client_http_scheme = http
diff --git a/regression-test/suites/cold_heat_separation/policy/alter.groovy 
b/regression-test/suites/cold_heat_separation/policy/alter.groovy
index cfd8e0da6fc..22903e672ba 100644
--- a/regression-test/suites/cold_heat_separation/policy/alter.groovy
+++ b/regression-test/suites/cold_heat_separation/policy/alter.groovy
@@ -174,7 +174,7 @@ suite("alter_policy") {
         // AWS_CONNECTION_TIMEOUT_MS
         assertEquals(show_alter_result[5][3], "2222")
         // AWS_ENDPOINT
-        assertEquals(show_alter_result[6][3], "http://bj.s3.comaaaa";)
+        assertEquals(show_alter_result[6][3], "bj.s3.comaaaa")
         // AWS_REGION
         assertEquals(show_alter_result[7][3], "bj")
         // s3_rootpath


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to